Italian (Italiano) translation by Loris Pizii (you can also view the original English article)

Questo tutorial è parte della serie Building Your Startup con PHP su Envato Tuts. In questa serie, io sto guidando l'utente attraverso lanciando un avvio dal concetto alla realtà utilizzando la mia app Meeting Planner come un esempio di vita reale. Ogni passo lungo la strada, io rilascio il codice di Meeting Planner come esempi di open source da che si può imparare. Mi rivolgo anche problemi relativi avvio business che si presentano.
Mobile Apps vs il Responsive Web
Strategicamente, creazione di applicazioni mobile per Meeting Planner su iOS e Android ha un senso, ma finanziariamente non ho sollevato le risorse per questo ancora. Mathew Ingram ha scritto recentemente nella fortuna che a causa della pletora di offerte per gli utenti mobili, "statisticamente parlando almeno — nessuno sta per scaricare l'app." Così mentre certamente potuto migliorare l'esperienza di Meeting Planner con un'app, sua probabilità di adozione non ha senso immediato con le mie risorse corrente.
Tuttavia, è estremamente importante rendere Meeting Planner un'esperienza di grande web sui dispositivi mobili.
Nella puntata di oggi, io esaminare e discutere le modifiche orientate a fare proprio questo — essenzialmente rendendo la nostra applicazione web più di un sito Web reattivo, facilmente utilizzabile su dispositivi mobili e tablet. Scopri i risultati (sul tuo telefono cellulare o tablet)!
Una delle sfide che codifica per episodio l'odierno era che io non sono un designer o programmatore CSS. Alcuni giorni mi sento come se io non dovrei nemmeno essere codifica me stesso; a Microsoft sono stato un Group Program Manager, cioè abbiamo avuto graphic designer, un laboratorio di usabilità completamente attrezzato, CSS non esiste, ecc.
Che conduce fino a questo lavoro, mi sono sentito intimidito cercando di imparare le media query, i punti di interruzione e specializzati CSS — non si tratta di una questione di soggetto sono esperto a, ed è molto tempo e molto orientato al dettaglio. Ancora, entro 48 ore, tutto è venuto insieme rapidamente e splendidamente. Se si esegue la scansione a fondo la storia, vedrai quante poche righe di CSS in ultima analisi, sono stati necessari per tutte le modifiche. Improvvisamente, come ho cominciato a sfogliare Meeting Planner sul mio telefono, sono cresciuto eccitato per quanto bene la nuova esperienza di reattivo web stava lavorando.
Francamente, mi ha fatto sentire che un app mobile dedicato solo non è necessario in questo momento. Possiamo costruire il nostro pubblico con l'esperienza web mobile per ora, soprattutto attraverso le fasi critiche di alfa e beta imminente.
Nel frattempo, se ancora non avete provato Meeting Planner, andare avanti e pianificare il tuo primo incontro dal tuo telefono o tablet. Ho partecipare nel thread di commenti qui sotto, così mi racconta la tua esperienza! Si può anche contattarmi su Twitter @reifman. Io sono sempre interessato a nuove funzionalità richieste e suggerite argomenti di esercitazione.
Come promemoria, tutto il codice per Meeting Planner è scritto in Yii2 Framework per PHP. Se si desidera ulteriori informazioni Yii2, Scopri la nostra serie parallela programmazione con Yii2.
Lo stato corrente Mobile
Per iniziare, ho navigato lo stato corrente del servizio Meeting Planner con il mio telefono di iOS e ha preso gli screenshots dell'applicazione iniziale. Non è terribile, ma non è grande. Passiamo in rassegna quello che ho trovato.
La Home Page
La home page sembra buona, anche se esteticamente vorrei che il testo di piombo, "Rendendo pianificazione facile", si rompe in modo un po' diverso, vale a dire su tre linee di lunghezza approssimativamente uguale. Tuttavia, Bootstrap gestisce bene il menu a discesa, e il resto della pagina funziona funzionalmente:

La pagina di iscrizione
Di nuovo, diverso da layout estetico di titolo e consistenza del margine sinistro, nella pagina di registrazione è fondamentalmente funzionale:

Riunioni di pianificazione
Una volta che la persona comincia alle riunioni di programmazione, la pagina di indice corrente ha bisogno di miglioramento. Ci sono troppe colonne. Il soggetto è troppo angusto. Forse le informazioni che ho scelto di visualizzare qui in primo luogo non sono essenziale. E, certamente, le opzioni di comando non sono in vista. La pagina deve essere regolata per mobile più significativamente.

Altra funzione di pagine ben, come ad esempio la nuova richiesta di riunione per un soggetto. Tuttavia, gli utenti mobili probabilmente non vuole essere offerto un campo textarea digitare un messaggio più lungo, introducendo l'incontro:

Aggiungendo i partecipanti diventa anche un po' disfunzionale con le estensioni bootstrap che stiamo usando:

E la vista progettazione per luoghi e tempi comincia ad abbattere. Ancora una volta, il design desktop offre troppi dettagli e troppe opzioni per mobile:

Altri settori
La pagina luoghi funzionalmente funziona ma ha bisogno di un migliore layout dei pulsanti. E forse questa funzionalità non è necessaria per gli utenti mobili.

Analogamente, il layout di scheda e foto desktop si rompe su mobile. Ha anche bisogno di essere ripensato:

Lo sviluppo di soluzioni
Certamente, ci sono un sacco di aree del sito che possono essere migliorate. Alcune aree hanno bisogno di essere ripensato per mobile, alcuni ridotti al minimo, e altri solo esteticamente regolato. Mettiamoci al lavoro.
Diversi approcci
Ho avuto praticamente zero esperienza con media query e i punti di interruzione quando ho iniziato questa attività. Per pochi giorni prima, ho procrastinato oltre tuffarsi in ciò che temevo era un pantano non conosce. Ho cominciato con una query di media di pratica per prendere in giro il mio editore:
@media only life and (max-energy-level: 60%) and (-caffeine-ratio: 2) { .editorBossperson { available-to:false; visible-to:false; } }
Scherzando intorno pausa ci ha aiutato il ghiaccio mentale nella mia testa. Io sono sempre disponibili e visibili agli dèi editoriale di Envato.
C'erano una serie di settori che ho cominciato a considerare:
- Semplificando la funzionalità, soprattutto con il processo di pianificazione di riunione
- Identificare le informazioni critiche da visualizzare per mobile
- Nascondere alcune funzionalità sui dispositivi mobili, quali elementi, colonne e opzioni di menu
- Lavorare con media-query e i punti di interruzione
- Rimanere concentrati sulle aree più importanti per la release alpha
Un concetto utile, che continuato a correre in sul web è stato "Design Mobile prima." Purtroppo, io sono vecchia scuola e non l'avesse fatto. Ma è stato utile a ripensare ogni pagina con questo tema: il primo Mobile.
Ad esempio, l'indice di riunione con quattro colonne della tabella doveva andare ed era disorientante sui telefoni di ritratto.
Continuavo a chiedermi come avrei progettato tutte le pagine di lavorare da un telefono cellulare.
Riscaldamento fino a Media query
Menù a tendina
Mi ci è voluto qualche sforzo per superare la mia esitazione a tuffarsi nel profondo CSS. Per scaldare, ho iniziato a lavorare riducendo al minimo i menu a discesa e semplificare l'ambito della funzionalità per dispositivi mobili.
Per ora, ho deciso di creare una singola base media query per dispositivi di piccole dimensioni e l'uso che in tutto il sito. Qui è frontend/site.css:
/* ----------- mobile displays ----------- */ @media only screen and (min-device-width: 320px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) { /* hides drop down menu items and footer items */ .itemHide,li.menuHide { display:none; visible:false; }
Apportando modifiche si è rivelate per essere relativamente semplice. Per qualsiasi voce di menu che ho voluto nascondere il cellulare, dovevo solo aggiungere una proprietà CSS, ad esempio menuHide.
Qui è la proprietà di menuHide
aggiunta alla /frontend/views/layouts/main.php:
$menuItems[] = [ 'label' => 'Account', 'items' => [ [ 'label' => Yii::t('frontend','Places'), 'url' => ['/place/yours'], 'options'=>['class'=>'menuHide'], ], [ 'label' => Yii::t('frontend','Friends'), 'url' => ['/friend'], 'options'=>['class'=>'menuHide'], ], [ 'label' => Yii::t('frontend','Profile'), 'url' => ['/user-profile'], 'options'=>['class'=>'menuHide'], ], [ 'label' => Yii::t('frontend','Contact information'), 'url' => ['/user-contact'], 'options'=>['class'=>'menuHide'], ], [ 'label' => Yii::t('frontend','Settings'), 'url' => ['/user-setting'], //'options'=>['class'=>'menuHide'], ], [ 'label' => Yii::t('frontend','Reminders'), 'url' => ['/reminder'], 'options'=>['class'=>'menuHide'], ], [ 'label' => Yii::t('frontend','Logout').' (' . \common\components\MiscHelpers::getDisplayName(Yii::$app->user->id) . ')', 'url' => ['/site/logout'], 'linkOptions' => ['data-method' => 'post'] ], ], ]; echo Nav::widget([ 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => $menuItems, ]);
Improvvisamente, il menu a discesa aveva meno complessità:

Poco a poco, mi sono reso conto che semplificando e riducendo la funzionalità del Web mobile sarebbe creare la migliore esperienza. Persone può sempre tornare al proprio desktop per accedere alle altre funzioni, almeno per ora. Questo sarebbe anche un'opportunità per raccogliere feedback da persone durante le fasi di alpha e beta.
Mollica di pane
Layout predefinito di Yii include un widget di navigazione che viene caricato tramite compositore e più difficile da personalizzare. Ho sperimentato con l'aggiunta di CSS per nascondere il primo elemento e il primo divisore "/":

Esso ha preso qualche tempo ma mi ha fatto immersioni più profonde in CSS, contenuto ad esempio ennesima-bambino e costruito la mia fiducia:
/* removes home and / from breadcrumb */ ul.breadcrumb li:first-child, li.tabHide { display:none; visible:false; } ul.breadcrumb li:nth-child(2)::before { content:''; }
Non avevo idea che CSS potrebbe modificare il contenuto.
Ecco il risultato:

Spaziatura pulsante avanzata per la punta delle dita
Successivamente, ho aggiunto CSS per fornire imbottitura supplementare per i pulsanti sul mobile per rendere polpastrello presse meno soggetto a errori. Ad esempio, qui ci sono i pulsanti Invia e Annulla sui dispositivi desktop:

Questo è il CSS ho usato e ha iniziato l'aggiunta di vari pulsanti e icone cliccabili attorno al sito:
/* fingertip spacing for buttons */ a.icon-pad { padding: 0 5px 0 2px; } .button-pad { padding-left:7px; }
Ecco l'aspetto che si formano sul telefonino — si noti la nuova spaziatura tra Invia e Annulla:

Disposizione del testo reattivo

Rendendo l'intestazione della pagina home, "Programmazione Made Easy," avvolgere in realtà si è rivelato a prendere un po' più tempo. In definitiva, ho aggiunto un<br/>
Tag per il testo e lo ha nascosto per impostazione predefinita quando non è sul cellulare. Ma ho anche dovuto aggiungere uno spazio in un tag span con la classe itemHide
.
<h1> <?php echo Yii::t('frontend','Scheduling'); ?> <br class="rwd-break" /> <span class="itemHide"> </span> <?php echo Yii::t('frontend','Made Easy') ?> </h1>
Ecco il codice CSS per. RWD-pausa.
È nascosto per impostazione predefinita e viene visualizzata solo in display reattivo, rompendo il testo di intestazione nel modo desiderato.
.rwd-break { display:none; } /* ----------- mobile displays ----------- */ @media only screen and (min-device-width: 320px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) { ... .rwd-break { display:block; } }
Senza lo spazio tag span, il testo sarebbe rompere senza centraggio corretto.
Semplificare la pagina di elenco di riunioni
Come pensavo "primo mobile" più, mi sono reso conto che l'utente telefonico non ha bisogno di tutte le funzionalità sulle mie pagine. Non hanno bisogno di tutte le schede, non hanno bisogno della tabella di dati relativi a riunioni e non hanno bisogno di tutte le opzioni del pulsante icona. Infatti, per la pagina di riunione, hanno solo bisogno di essere in grado di aprire le riunioni (possono annullare riunioni dal vista riunione pagina stessa).
Ho combinato il soggetto e il partecipante colonne in una singola colonna verticale, e il risultato sembra molto meglio.

In /frontend/views/meeting/index.php, ho aggiunto .tabHide
a due delle quattro schede:
<!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> <li class="active"><a href="#planning" role="tab" data-toggle="tab">Planning</a></li> <li ><a href="#upcoming" role="tab" data-toggle="tab">Confirmed</a></li> <li class="tabHide"><a href="#past" role="tab" data-toggle="tab" >Past</a></li> <li class="tabHide"><a href="#canceled" role="tab" data-toggle="tab">Canceled</a></li> </ul>
E, in /frontend/views/meeting/_grid.php, ho ristrutturato la colonna per combinare soggetto e partecipante:
if ($mode =='upcoming' || $mode =='past') { echo GridView::widget([ 'dataProvider' => $dataProvider, //'filterModel' => $searchModel, 'columns' => [ [ 'label'=>'Details', 'attribute' => 'meeting_type', 'format' => 'raw', 'value' => function ($model) { // to do - remove legacy code when subject didn't exist if ($model->subject=='') { return '<div><a href="'.Url::to(['meeting/view', 'id' => $model->id]).'">'.$model->getMeetingHeader().'</a><br /><span class="index-participant">'.$model->getMeetingParticipants($model->id).'</span></div>'; } else { return '<div><a href="'.Url::to(['meeting/view', 'id' => $model->id]).'">'.$model->subject.'</a><br /><span class="index-participant">'.$model->getMeetingParticipants($model->id).'</span></div>'; } }, ],
Nascondendo la ActionColumn
necessaria un po' di ricerca, ma assomiglia a questo:
['class' => 'yii\grid\ActionColumn','header'=>'Options','template'=>'{view} {decline} {cancel}', 'headerOptions' => ['class' => 'itemHide'], 'contentOptions' => ['class' => 'itemHide'], 'buttons'=>[ 'view' => function ($url, $model) { return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', $url, [ 'title' => Yii::t('frontend', 'view'), 'class' => 'icon-pad', ]); }, 'decline' => function ($url, $model) { return ($model->status==$model::STATUS_SENT ) ? Html::a('<span class="glyphicon glyphicon-thumbs-down"></span>', $url, [ 'title' => Yii::t('frontend', 'decline'), 'class' => 'icon-pad', ]) : ''; }, 'cancel' => function ($url, $model) { return ($model->status==$model::STATUS_SENT || $model->status==$model::STATUS_CONFIRMED ) ? Html::a('<span class="glyphicon glyphicon-remove-circle"></span>', $url, [ 'title' => Yii::t('frontend', 'cancel'), 'data-confirm' => Yii::t('frontend', 'Are you sure you want to cancel this meeting?'), 'class' => 'icon-pad', ]) : ''; }, ] ],
In definitiva, questi cambiamenti semplificato l'interfaccia desktop nel processo di miglioramento mobile.
La grande sfida: Incontro di programmazione

Di gran lunga il compito più impegnativo per me era di adattare la pagina sopra per mobile di pianificazione delle riunioni. E ' stato un pasticcio sui telefoni, ed ero spaventare. Separatamente, io ho sempre preoccupato come avrebbe adottato questa interfaccia per più partecipanti in futuro — requisiti reattivi potrebbero fare questo più difficile.
Il mio utilizzo di estensione di Bootstrap Switch Widget di Kartik per Yii ha le proprie limitazioni quando si tratta di modifica layout. Posizionamento di questi elementi nelle colonne della tabella ha funzionato bene, ma rendendo le colonne della tabella reattiva non era così semplice con media query.
Certamente, come ho mostrato con la pagina di elenco riunioni sopra, nascondere colonne è facile, ma modificando il posizionamento non tanto.
Ho cominciato allontanandosi da una progettazione tabella orizzontale per mostrare le opzioni di tempo e luogo e verso uno stile ritratto verticale. E, a quanto pare, tabelle e colonne hanno le proprie capacità di avvolgere con HTML5 e CSS senza media query.
Si può vedere il piano di riunione migliorata, vuota pagina qui:

Ogni visualizzazione parziale richiesto ulteriori css colonne per layout predefiniti griglia Bootstrap lavorare bene, col-xs4 es. sinistra e destra col-xs-8. Ecco un esempio:
<div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <div class="row"> <div class="col-lg-4 col-md-4 col-xs-4"><h4>What</h4></div> <div class="col-lg-8 col-md-8 col-xs-8"><div style="float:right;"> <?php if ($isOwner) { echo Html::a('', ['update', 'id' => $model->id], ['class' => 'btn btn-primary glyphicon glyphicon-pencil','title'=>'Edit']); } ?> </div> </div> </div> </div>
Rendendo il luogo e tempo forme reattive di pianificazione è stata la più difficile. Ho sperimentato e alla fine è riuscito a usando le colonne della tabella che naturalmente avvolgono come lo strizzacervelli contenuto finestra (o dispositivo).
Ho eliminato anche visualizzare lo stato del partecipante nella propria colonna con interruttori disabili — non è possibile modificarle, quindi perche ' mostrare loro come interruttori? Invece, ho creato un riassunto testuale dello status dei partecipanti per luoghi e tempi. Ecco il codice per getWhenStatus():
public static function getWhenStatus($meeting,$viewer_id) { // get an array of textual status of meeting times for $viewer_id // Acceptable / Rejected / No response: $whenStatus['text'] = []; $whenStatus['style'] = []; foreach ($meeting->meetingTimes as $mt) { // build status for each time $acceptableChoice=[]; $rejectedChoice=[]; $unknownChoice=[]; // to do - add meeting_id to MeetingTimeChoice for sortable queries foreach ($mt->meetingTimeChoices as $mtc) { if ($mtc->user_id == $viewer_id) continue; switch ($mtc->status) { case MeetingTimeChoice::STATUS_UNKNOWN: $unknownChoice[]=$mtc->user_id; break; case MeetingTimeChoice::STATUS_YES: $acceptableChoice[]=$mtc->user_id; break; case MeetingTimeChoice::STATUS_NO: $rejectedChoice[]=$mtc->user_id; break; } } $temp =''; // to do - update for multiple participants // to do - integrate current setting for this user in style setting if (count($acceptableChoice)>0) { $temp.='Acceptable to '.MiscHelpers::getDisplayName($acceptableChoice[0]); $whenStatus['style'][$mt->id]='success'; } else if (count($rejectedChoice)>0) { $temp.='Rejected by '.MiscHelpers::getDisplayName($rejectedChoice[0]); $whenStatus['style'][$mt->id]='danger'; } else if (count($unknownChoice)>0) { $temp.='No response from '.MiscHelpers::getDisplayName($unknownChoice[0]); $whenStatus['style'][$mt->id]='warning'; } $whenStatus['text'][$mt->id]=$temp; } return $whenStatus; }
Ecco come appare sul desktop — si noti il layout del paesaggio delle righe di testo e interruttori:

E qui è la versione mobile, più ritratto e impilate senza media query:

Ad esempio, qui è il CSS per il modo ho codificato le colonne della tabella sul quando pannello (volte):
table.table-list { width:100%; } table.table-list td.table-list-first { float: left; display: inline; width: auto; } table.table-list td.table-switches { width: auto; float: right; display: inline; padding-top: 10px; } .switch-pad { padding-left:7px; } .smallStatus { font-size:90%; color: grey; font-style: italic; }
Ed ecco il codice per questa forma parziale da /frontend/views/meeting-time/_list.php:
<?php use yii\helpers\Html; use frontend\models\Meeting; use \kartik\switchinput\SwitchInput; ?> <tr > <!-- panel row --> <td > <table class="table-list"> <!-- list of times --> <tr> <td class="table-list-first"> <!-- time & status --> <?= Meeting::friendlyDateFromTimestamp($model->start,$timezone) ?> <?php if ($whenStatus['text'][$model->id]<>'') { ?> <br /><span class="smallStatus"> <?php echo $whenStatus['text'][$model->id]; ?> </span><br /> <?php } ?> </td> <td class="table-switches"> <!-- col of switches to float right --> <table > <tr> <td > <?php if ($isOwner) { showTimeOwnerStatus($model,$isOwner); } else { showTimeParticipantStatus($model,$isOwner); } ?> </td> <td class="switch-pad"> <?php if ($timeCount>1) { if ($model->status == $model::STATUS_SELECTED) { $value = $model->id; } else { $value = 0; } if ($isOwner || $participant_choose_date_time) { // value has to match for switch to be on echo SwitchInput::widget([ 'type' => SwitchInput::RADIO, 'name' => 'time-chooser', 'items' => [ [ 'value' => $model->id], ], 'value' => $value, 'pluginOptions' => [ 'size' => 'mini','handleWidth'=>60,'onText' => '<i class="glyphicon glyphicon-ok"></i> choose','onColor' => 'success','offText'=>'<i class="glyphicon glyphicon-remove"></i>'], // $whenStatus['style'][$model->id], 'labelOptions' => ['style' => 'font-size: 12px'], ]); } } ?> </td> </tr> </table> </td> <!-- end col with table of switches --> </tr> </table> <!-- end table list of times --> </td> </tr> <!-- end panel row -->
La cosa migliore di queste riunioni Visualizza modifiche è che ti semplificano la sfida di progettazione UX per futuri incontri con molti partecipanti. Indipendentemente dal numero di persone in una riunione, la vista rimarrà fondamentalmente lo stesso come sopra. Essenzialmente, questo risolto il maggior ostacolo per me espansione a più riunioni partecipante — progettazione UX.
Quali sono le prospettive?
Spero che ti sia piaciuta in seguito lungo come io lavoro sulle minuzie del responsive web design. Come il codice e cambiamenti visivi al sito si sono riuniti, che mi sentivo estremamente soddisfatto e colpito con CSS quanto poco è stato richiesto. Presi insieme, potete vederlo qui:
.rwd-break { display:none; } table.table-list { width:100%; } table.table-list td.table-list-first { float: left; display: inline; width: auto; } table.table-list td.table-switches { width: auto; float: right; display: inline; padding-top: 10px; } .switch-pad { padding-left:7px; } .smallStatus { font-size:90%; color: grey; font-style: italic; } .setting-label label, #preferences label { font-weight:normal; } /* ----------- mobile displays ----------- */ @media only screen and (min-device-width: 320px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) { /* hides drop down menu items and footer items */ .itemHide,li.menuHide { display:none; visible:false; } /* removes home and / from breadcrumb */ ul.breadcrumb li:first-child, li.tabHide { display:none; visible:false; } ul.breadcrumb li:nth-child(2)::before { content:''; } /* fingertip spacing for buttons */ a.icon-pad { padding: 0 5px 0 2px; } .button-pad { padding-left:7px; } .rwd-break { display:block; } }
Miei sforzi futuro design inizierà, "Che dovrebbe questo aspetto sul cellulare?"
Come accennato, attualmente sto lavorando febbrilmente per preparare Meeting Planner per rilascio alpha. Mi sono concentrato principalmente sui principali miglioramenti e caratteristiche che renderanno l'alfa rilasciare il tasto go senza intoppi.
Sto tracciando tutto in Asana ora, che io scrivere in un altro tutorial; è stato incredibilmente utile. Ci sono anche alcune nuove interessanti caratteristiche ancora sulla loro strada.
Sto anche cominciando a concentrarsi maggiormente sull'investimento prossimo raduno sforzo con Meeting Planner. Sto appena cominciando a sperimentare con WeFunder basato sull'attuazione delle nuove regole di crowdfunding della SEC. Si prega di considerare seguendo il nostro profilo. Anche io scriverò più su questo in un futuro tutorial.
Ancora una volta, mentre si aspetta per più episodi, pianificare il tuo primo incontro (dal vostro telefono!). Inoltre, lo apprezzerei se condividete la vostra esperienza qui sotto nei commenti, e io sono sempre interessato a vostri suggerimenti. Si può anche contattarmi su Twitter @reifman direttamente. È inoltre possibile inviare presso il sito di supporto di Meeting Planner.
Guarda per i prossimi tutorial della serie Building Your Startup con PHP.
Link correlati
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post