Stimulus-apps vertalen met I18Next

In mijn vorige artikel behandelde ik Stimulus - een bescheiden JavaScript-framework gemaakt door Basecamp. Vandaag zal ik het hebben over het internationaliseren van een Stimulus-applicatie, aangezien het framework geen I18n-tools uit de doos biedt. Internationalisering is een belangrijke stap, vooral wanneer uw app wordt gebruikt door mensen van over de hele wereld, dus een basiskennis van hoe u dit kunt doen, kan echt van pas komen.

Het is natuurlijk aan u om te beslissen welke internationalisatie-oplossing moet worden geïmplementeerd, of het nu jQuery.I18n, Polyglot of een ander is. In deze tutorial wil ik je een populair I18n-framework laten zien met de naam I18next, dat veel coole functies bevat en veel extra externe plug-ins biedt om het ontwikkelingsproces nog verder te vereenvoudigen. Zelfs met al deze functies is I18next geen complexe tool en hoeft u niet veel documentatie te bestuderen om aan de slag te gaan.

In dit artikel leert u hoe u I18n-ondersteuning in Stimulus-toepassingen kunt inschakelen met behulp van de I18next-bibliotheek. Concreet zullen we het hebben over:

  • I18volgende configuratie
  • vertaalbestanden en asynchroon laden
  • vertalingen uitvoeren en de hele pagina in één keer vertalen
  • werken met meervouden en genderinformatie
  • schakelen tussen locales en het voortzetten van de gekozen locale in de GET-parameter
  • locale instellen op basis van de voorkeuren van de gebruiker

De broncode is beschikbaar in de tutorial GitHub repo.

Bootstrappen met een Stimulus-app

Laten we om aan de slag te gaan het Stimulus Starter-project klonen en alle afhankelijkheden installeren met behulp van de Yarn-pakketbeheerder:

git clone https://github.com/stimulusjs/stimulus-starter.git cd stimulus-starter garen installeren

We gaan een eenvoudige webtoepassing bouwen die informatie over de geregistreerde gebruikers laadt. Voor elke gebruiker tonen we zijn login en het aantal foto's dat hij of zij tot nu toe heeft geüpload (het maakt niet echt uit wat deze foto's zijn). 

We gaan ook een taalwisselaar presenteren aan de bovenkant van de pagina. Wanneer een taal wordt gekozen, moet de interface meteen worden vertaald zonder herladen van de pagina. Bovendien moet de URL worden toegevoegd met een ?locale GET-parameter die aangeeft welke landinstelling momenteel wordt gebruikt. Als de pagina is geladen met deze parameter, moet de juiste taal natuurlijk automatisch worden ingesteld.

Oké, laten we doorgaan met het weergeven van onze gebruikers. Voeg de volgende regel code toe aan de public / index.html het dossier:

Hier gebruiken we de gebruikers controller en het verstrekken van een URL van waaruit onze gebruikers kunnen worden geladen. In een echte toepassing zouden we waarschijnlijk een server-side script hebben dat gebruikers uit de database haalt en antwoordt met JSON. Laten we voor deze zelfstudie gewoon alle benodigde gegevens in de public / api / users / index.json het dossier:

["login": "johndoe", "photos_count": "15", "gender": "male", "login": "annsmith", "photos_count": "20", "gender": "vrouw "] 

Maak nu een nieuw src / controllers / users_controller.js het dossier:

import Controller uit "stimulus" export standaardklasse verlengt Controller connect () this.loadUsers ()

Zodra de controller is verbonden met de DOM, laden we onze gebruikers asynchroon met behulp van de loadUsers () methode:

 loadUsers () fetch (this.data.get ("url")) .then (response => response.text ()) .then (json => this.renderUsers (json))

Deze methode verzendt een ophaalopdracht naar de opgegeven URL, pakt het antwoord en geeft de gebruikers uiteindelijk:

 renderUsers (gebruikers) let content = "JSON.parse (gebruikers) .forEach ((user) => content + = '
Inloggen: $ user.login
Heeft $ user.photos_count foto ('s) geüpload

') this.element.innerHTML = content

renderUsers (), beurtelings, analyseert JSON, construeert een nieuwe reeks met alle inhoud, en toont uiteindelijk deze inhoud op de pagina (this.element gaat het eigenlijke DOM-knooppunt waarop de controller is aangesloten teruggeven, wat wel zo is div in ons geval).

I18next

Nu gaan we verder met het integreren van I18next in onze app. Voeg twee bibliotheken toe aan ons project: I18next zelf en een plug-in om asynchroon laden van vertaalbestanden vanaf de achterkant mogelijk te maken:

garen toevoegen i18next i18next-xhr-backend

We gaan alle I18next-gerelateerde dingen apart opslaan src / i18n / Config.js bestand, dus maak het nu:

import i18next from 'i18next' import I18nXHR van 'i18next-xhr-backend' const i18n = i18next.use (I18nXHR) .init (fallbackLng: 'en', witte lijst: ['en', 'ru'], preload: [ 'en', 'ru'], ns: 'gebruikers', defaultNS: 'gebruikers', fallbackNS: false, debug: true, backend: loadPath: '/ i18n / lng / ns. json ',, functie (err, t) if (err) return console.error (err)); exporteren i18n als i18n

Laten we van boven naar beneden gaan om te begrijpen wat hier gebeurt:

  • Gebruik (I18nXHR) maakt de plug-in i18next-xhr-backend mogelijk.
  • fallbackLng vertelt het om Engels te gebruiken als een fallback-taal.
  • whitelist laat alleen Engelse en Russische talen instellen. U kunt natuurlijk ook andere talen kiezen.
  • preload instrueert dat vertaalbestanden vooraf moeten worden geladen vanaf de server in plaats van ze te laden wanneer de overeenkomstige taal is geselecteerd.
  • NS betekent "naamruimte" en accepteert een tekenreeks of een array. In dit voorbeeld hebben we slechts één naamruimte, maar voor grotere toepassingen kunt u andere naamruimten introduceren, zoals beheerderkar, profiel, enz. Voor elke naamruimte moet een afzonderlijk vertaalbestand worden gemaakt.
  • defaultNS sets gebruikers om de standaardnaamruimte te zijn.
  • fallbackNS schakelt namespace fallback uit.
  • debug zorgt ervoor dat foutopsporingsinformatie wordt weergegeven in de browserconsole. In het bijzonder wordt aangegeven welke vertaalbestanden worden geladen, welke taal is geselecteerd, enzovoort. Waarschijnlijk wilt u deze instelling uitschakelen voordat u de toepassing naar productie implementeert.
  • backend biedt configuratie voor de I18nXHR-plug-in en geeft aan waar vandaan vertalingen moeten worden geladen. Merk op dat het pad de titel van de locale moet bevatten, terwijl het bestand naar de naamruimte moet worden genoemd en de .json uitbreiding
  • functie (err, t) is de callback die moet worden uitgevoerd als I18next gereed is (of als er een fout is opgetreden).

Laten we vervolgens vertaalbestanden maken. Vertalingen voor de Russische taal moeten in de public / i18n / ru / users.json het dossier:

"login": "Логин"

Log in hier is de vertaalsleutel, terwijl Логин is de waarde die moet worden weergegeven.

Engelse vertalingen moeten op hun beurt naar de public / i18n / nl / users.json het dossier:

 "Log in log in" 

Om ervoor te zorgen dat I18next werkt, kunt u de volgende regel code toevoegen aan de callback binnen de i18n / Config.js het dossier:

// config gaat hier ... functie (err, t) if (err) return console.error (err) console.log (i18n.t ('login'))

Hier gebruiken we een methode genaamd t dat betekent "vertalen". Deze methode accepteert een vertaalsleutel en retourneert de bijbehorende waarde.

We kunnen echter veel delen van de gebruikersinterface hebben die moeten worden vertaald, en dit doen door gebruik te maken van de t methode zou nogal vervelend zijn. In plaats daarvan raad ik aan om een ​​andere plugin genaamd loc-i18next te gebruiken waarmee je meerdere elementen tegelijk kunt vertalen.

Vertalen in één keer

Installeer de loc-i18next plug-in:

garen voeg loc-i18next toe

Importeer het bovenaan de src / i18n / Config.js het dossier:

import locI18next van 'loc-i18next'

Geef nu de configuratie voor de plug-in zelf:

// andere configuratie const loci18n = locI18next.init (i18n, selectorAttr: 'data-i18n', optionsAttr: 'data-i18n-options', useOptionsAttr: true); exporteren loci18n as loci18n, i18n as i18n

Er zijn een paar dingen om op te merken:

  • locI18next.init (i18n) maakt een nieuw exemplaar van de plug-in gebaseerd op het eerder gedefinieerde exemplaar van I18next.
  • selectorAttr geeft aan welk attribuut moet worden gebruikt om elementen te detecteren die moeten worden gelokaliseerd. In principe gaat loc-i18next naar dergelijke elementen zoeken en de waarde van de gebruiken data-i18n attribuut als de vertaalsleutel.
  • optionsAttr geeft aan welk attribuut extra vertaalopties bevat.
  • useOptionsAttr instrueert de plug-in om de aanvullende opties te gebruiken.

Onze gebruikers worden asynchroon geladen, dus we moeten wachten totdat deze bewerking is voltooid en pas daarna de lokalisatie uitvoeren. Laten we voor nu gewoon een timer instellen die twee seconden moet wachten voordat hij de lokaliseren() methode - dat is natuurlijk een tijdelijke hack.

 import loci18n van '... / i18n / config' // andere code ... loadUsers () fetch (this.data.get ("url")) .then (response => response.text ()) .then (json => this.renderUsers (json) setTimeout (() => // <--- this.localize() , '2000') ) 

Codeer de lokaliseren() methode zelf:

 localize () loci18n ('. users')

Zoals je ziet, hoeven we alleen een selector door te geven aan de loc-i18next-plug-in. Alle elementen binnen (die de data-i18n attribuutset) zal automatisch worden gelokaliseerd.

Nu tweak renderUsers methode. Laten we voorlopig alleen het woord "Login" vertalen:

 renderUsers (gebruikers) let content = "JSON.parse (gebruikers) .forEach ((user) => content + = '
ID: $ user.id
: $ user.login
Heeft $ user.photos_count foto ('s) geüpload

') this.element.innerHTML = content

Leuk! Herlaad de pagina, wacht twee seconden en zorg ervoor dat het woord "Login" voor elke gebruiker verschijnt.

Meervouden en geslacht

We hebben een deel van de interface gelokaliseerd, wat echt cool is. Toch heeft elke gebruiker nog twee velden: het aantal geüploade foto's en geslacht. Omdat we niet kunnen voorspellen hoeveel foto's elke gebruiker zal hebben, moet het woord 'foto' op basis van de gegeven telling correct worden gepreceerd. Om dit te doen, hebben we een data-i18n-opties attribuut eerder geconfigureerd. Om de telling te geven, data-i18n-opties moet worden toegewezen met het volgende object: "count": YOUR_COUNT.

Genderinformatie moet ook in overweging worden genomen. Het woord 'geüpload' in het Engels kan zowel op mannelijk als vrouwelijk worden toegepast, maar in het Russisch wordt het 'загрузил' of 'загрузила', dus we hebben het nodig data-i18n-opties nogmaals, wat heeft "context": "GENDER" als een waarde. Merk overigens op dat je deze context kunt gebruiken om andere taken te bereiken, niet alleen om genderinformatie te geven.

 renderUsers (gebruikers) let content = "JSON.parse (gebruikers) .forEach ((user) => content + = '
: $ user.login

') this.element.innerHTML = content

Update nu de Engelse vertalingen:

"login": "Inloggen", "geüpload": "Heeft geüpload", "foto's": "één foto", "photos_plural": "count foto's"

Niets ingewikkelds hier. Omdat we ons in het Engels niets aantrekken van de genderinformatie (de context), zou de vertaalsleutel eenvoudig moeten zijn geüpload. Om correct geperverteerde vertalingen te bieden, gebruiken we de foto's en photos_plural sleutels. De Count deel is interpolatie en wordt vervangen door het daadwerkelijke aantal.

Wat betreft de Russische taal, dingen zijn ingewikkelder:

"login": "Логин", "uploaded_male": "Загрузил уже", "uploaded_female": "Загрузила уже", "photos_0": "одну фотографию", "photos_1": "count фотографии", " photos_2 ":" count фотографий " 

Merk allereerst op dat we beide hebben uploaded_male en uploaded_female toetsen voor twee mogelijke contexten. Vervolgens zijn pluralisatieregels ook complexer in het Russisch dan in het Engels, dus we moeten niet twee, maar drie mogelijke frases geven. I18next ondersteunt meerdere talen uit de verpakking en dit kleine hulpmiddel kan u helpen te begrijpen welke pluraliseringssleutels voor een bepaalde taal moeten worden gespecificeerd.

Landinstelling wisselen

We zijn klaar met het vertalen van onze applicatie, maar gebruikers moeten kunnen schakelen tussen locales. Voeg daarom een ​​nieuwe "language switcher" component toe aan de public / index.html het dossier:

    Maak de bijbehorende controller in de src / controllers / languages_controller.js het dossier:

    import Controller van "stimulus" import i18n, loci18n van '... / i18n / config' export standaardklasse verlengt Controller initialize () let languages ​​= [title: 'English', code: 'en', title: 'Русский', code: 'ru'] this.element.innerHTML = languages.map ((lang) => retourneer '
  • $ lang.title
  • '). join (")

    Hier gebruiken we de initialiseren () terugbellen om een ​​lijst met ondersteunde talen weer te geven. Elk li heeft een data-actie kenmerk dat aangeeft welke methode (switchLanguage, in dit geval) moet worden geactiveerd wanneer op het element wordt geklikt.

    Voeg nu de switchLanguage () methode:

     switchLanguage (e) this.currentLang = e.target.getAttribute ("data-lang")

    Het neemt eenvoudig het doelwit van het evenement en pakt de waarde van het data-lang attribuut.

    Ik zou ook een getter en een zetter voor de willen toevoegen currentLang attribuut:

     get currentLang () return this.data.get ("currentLang") stel currentLang (lang) if (i18n.language! == lang) i18n.changeLanguage (lang) if (this.currentLang! == lang ) this.data.set ("currentLang", lang) loci18n ('body') this.highlightCurrentLang ()

    De getter is heel eenvoudig: we halen de waarde van de momenteel gebruikte taal op en retourneren deze.

    De setter is complexer. Allereerst gebruiken we de changeLanguage methode als de momenteel ingestelde taal niet gelijk is aan de geselecteerde taal. We slaan ook de nieuw geselecteerde locale op onder de data-stroom-lang attribuut (dat wordt gebruikt in de getter), de body van de HTML-pagina lokaliseert met de loc-i18next-plug-in en als laatste de momenteel gebruikte locale markeert.

    Laten we de code coderen highlightCurrentLang ():

     highlightCurrentLang () this.switcherTargets.forEach ((el, i) => el.classList.toggle ("current", this.currentLang === el.getAttribute ("data-lang")))

    Hier zijn we bezig met een reeks locale switchers en het vergelijken van de waarden van hun data-lang schrijft toe aan de waarde van de momenteel gebruikte landinstelling. Als de waarden overeenkomen, wordt de switcher toegewezen met a stroom CSS-klasse, anders wordt deze klasse verwijderd.

    Om het te maken this.switcherTargets constructiewerk, we moeten Stimulus-doelen op de volgende manier definiëren:

    statische doelen = ["switcher"]

    Voeg ook toe data-target attributen met waarden van switcher voor de lis:

     initialize () // ... this.element.innerHTML = languages.map ((lang) => retourneer '
  • $ lang.title
  • '). join (") // ...

    Een ander belangrijk punt om te overwegen is dat het enige tijd duurt voordat vertaalbestanden zijn geladen en we moeten wachten totdat deze bewerking is voltooid voordat de locale kan worden gewijzigd. Laten we daarom profiteren van de loaded Bel terug:

     initialize () i18n.on ('loaded', (loaded) => // <--- let languages = [ title: 'English', code: 'en', title: 'Русский', code: 'ru' ] this.element.innerHTML = languages.map((lang) => return '
  • $ lang.title
  • '). join (") this.currentLang = i18n.language)

    Vergeet ten slotte niet te verwijderen setTimeout van de loadUsers () methode:

     loadUsers () fetch (this.data.get ("url")) .then (response => response.text ()) .then (json => this.renderUsers (json) this.localize ())

    Persisting Locale in de URL

    Nadat de locale is geschakeld, zou ik graag een ?LANG GET parameter naar de URL met de code van de gekozen taal. Het toevoegen van een GET-param zonder de pagina te herladen kan eenvoudig worden gedaan met behulp van de History API:

     set currentLang (lang) if (i18n.language! == lang) i18n.changeLanguage (lang) window.history.pushState (null, null, '? lang = $ lang') // <---  if(this.currentLang !== lang)  this.data.set("currentLang", lang) loci18n('body') this.highlightCurrentLang()  

    Locale detecteren

    Het laatste dat we vandaag gaan implementeren, is de mogelijkheid om de locale in te stellen op basis van de voorkeuren van de gebruiker. Een plug-in genaamd TaalDetector kan ons helpen deze taak op te lossen. Voeg een nieuw garenpakket toe:

    garen toevoegen i18next-browser-languagedetector

    Importeren LanguageDetector binnen in de i18n / Config.js het dossier:

    importeer LngDetector van 'i18next-browser-languagedetector'

    Wijzig nu de configuratie:

    const i18n = i18next.use (I18nXHR) .use (LngDetector) .init (// <--- // other options go here… detection:  order: ['querystring', 'navigator', 'htmlTag'], lookupQuerystring: 'lang',  , function(err, t)  if (err) return console.error(err) );

    De bestellen optie vermeldt alle technieken (gesorteerd op hun belangrijkheid) die de plug-in zou moeten proberen om de voorkeurslandinstelling te "raden":

    • querystring betekent het controleren van een GET-parameter die de code van de landinstelling bevat.
    • lookupQuerystring stelt de naam in van de GET-param die moet worden gebruikt, namelijk LANG in ons geval.
    • navigator betekent locale gegevens verkrijgen uit het verzoek van de gebruiker.
    • htmlTag omvat het ophalen van de voorkeurslandinstelling uit de LANG attribuut van de html label.

    Conclusie

    In dit artikel hebben we een kijkje genomen op I18next - een populaire oplossing om JavaScript-toepassingen eenvoudig te vertalen. U hebt geleerd I18next te integreren met het Stimulus-framework, het te configureren en vertaalbestanden op een asynchrone manier te laden. U hebt ook gezien hoe u kunt schakelen tussen landinstellingen en de standaardtaal kunt instellen op basis van de voorkeuren van de gebruiker.

    I18next heeft enkele extra configuratieopties en veel plug-ins, dus zorg ervoor dat u door de officiële documentatie bladert voor meer informatie. Merk ook op dat Stimulus je niet dwingt om een ​​specifieke lokaliseringsoplossing te gebruiken, dus je kunt ook proberen iets te gebruiken zoals jQuery.I18n of Polyglot. 

    Dat is alles voor vandaag! Bedankt voor het lezen, en tot de volgende keer.