Översätta Stimulus Apps med I18Next

I min tidigare artikel täckte jag Stimulus-ett blygsamt JavaScript-ramverk skapat av Basecamp. Idag ska jag prata om att internationalisera en Stimulus-applikation, eftersom ramverket inte ger några I18n-verktyg ur lådan. Internationalisering är ett viktigt steg, särskilt när din app används av människor från hela världen, så en grundläggande förståelse för hur man gör det kan verkligen vara till nytta.

Naturligtvis är det upp till dig att bestämma vilken internationaliseringslösning som ska genomföras, det vill säga jQuery.I18n, Polyglot eller någon annan. I denna handledning skulle jag vilja visa dig en populär I18n-ram kallad I18next som har massor av coola funktioner och ger många ytterligare plugin från tredje part för att förenkla utvecklingsprocessen ännu längre. Även med alla dessa funktioner är I18next inte ett komplext verktyg, och du behöver inte studera mycket dokumentation för att komma igång.

I den här artikeln lär du dig hur du aktiverar I18n-stöd i Stimulus-applikationer med hjälp av I18next-biblioteket. Specifikt talar vi om:

  • I18nästa konfiguration
  • översättningsfiler och laddar dem asynkront
  • utföra översättningar och översätta hela sidan på en gång
  • arbetar med plurals och könsinformation
  • växlar mellan lokaliteter och kvarstår den valda platsen i GET-parametern
  • inställning av landsting baserat på användarens inställningar

Källkoden finns i handledningen GitHub repo.

Bootstrapping a Stimulus App

För att komma igång, låt oss klona Stimulus Starter-projektet och installera alla beroenden med hjälp av Garn-pakethanteraren:

git klon https://github.com/stimulusjs/stimulus-starter.git cd-stimulus-start garn installera

Vi ska bygga en enkel webbapplikation som laddar information om de registrerade användarna. För varje användare visar vi hans / hennes inloggning och antalet bilder som han eller hon har laddat upp hittills (det spelar ingen roll vad de här bilderna är). 

Vi kommer också att presentera en språkomkopplare högst upp på sidan. När ett språk väljs ska gränssnittet översättas direkt utan att sidan laddas om. Dessutom bör webbadressen bifogas med en ?locale GET-parametern anger vilken lokal som används för närvarande. Självklart, om sidan är laddad med den här parametern som redan tillhandahållits, ska rätt språk ställas in automatiskt.

Okej, låt oss fortsätta att göra våra användare. Lägg till följande rad kod till publika / index.html fil:

Här använder vi användare controller och tillhandahålla en URL för att ladda våra användare. I en verklig applikation skulle vi förmodligen ha ett server-sidoskript som hämtar användare från databasen och svarar med JSON. För denna handledning, låt oss helt enkelt placera alla nödvändiga uppgifter i offentliga / api / användare / index.json fil:

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

Skapa nu en ny src / styrenheter / users_controller.js fil:

import Controller från "stimulus" export standardklass förlänger Controller connect () this.loadUsers ()

Så snart regulatorn är ansluten till DOM, laddar vi asynkront våra användare med hjälp av loadUsers () metod:

 loadUsers () hämta (this.data.get ("url")) .then (svar => response.text ()) .then (json => this.renderUsers (json))

Den här metoden skickar en hämtningsförfrågan till den angivna webbadressen, tar tag i svaret och ger slutligen användarna:

 renderUsers (users) let content = "JSON.parse (users) .forEach ((user) => content + = '
Logga in: $ user.login
Har laddat upp $ user.photos_count foto (er)

') this.element.innerHTML = innehåll

renderUsers (), parserar i sin tur JSON, konstruerar en ny sträng med allt innehåll och visar slutligen detta innehåll på sidan (this.element kommer att returnera den faktiska DOM noden som regulatorn är ansluten till, vilket är div i vårat fall).

I18next

Nu ska vi fortsätta att integrera I18next i vår app. Lägg till två bibliotek till vårt projekt: I18nästa sig och ett plugin för att möjliggöra asynkron lastning av översättningsfiler från baksidan:

garn lägg till i18next i18next-xhr-backend

Vi kommer att lagra alla I18next-relaterade saker i en separat src / i18n / config.js fil, så skapa den nu:

importera i18next från "i18next" import I18nXHR från "i18next-xhr-backend" const i18n = i18next.use (I18nXHR) .init (fallbackLng: 'en', whitelist: ['en', 'ru'], 'en', 'ru'], ns: 'användare', defaultNS: 'users', fallbackNS: false, debug: true, backend: loadPath: '/ i18n / lng / ns. json ',, funktion (err, t) om (err) returnerar console.error (err)); exportera i18n som i18n

Låt oss gå från topp till botten för att förstå vad som händer här:

  • använda (I18nXHR) möjliggör plugin för i18next-xhr-backend.
  • fallbackLng berättar att den ska använda engelska som backbackspråk.
  • vitlista tillåter endast engelska och ryska språk att ställas in. Naturligtvis kan du välja andra språk.
  • förbelastning instruerar översättningsfiler som ska förinstalleras från servern, istället för att ladda dem när motsvarande språk är valt.
  • ns betyder "namespace" och accepterar antingen en sträng eller en array. I det här exemplet har vi bara en namnrymd, men för större applikationer kan du introducera andra namnområden, som administrationvagn, profil, etc. För varje namnrymd ska en separat översättningsfil skapas.
  • Defaultns uppsättningar användare att vara standard namnrymd.
  • fallbackNS inaktiverar namespace-fallback.
  • debug tillåter felsökningsinformation att visas i webbläsarens konsol. Specifikt står det vilka översättningsfiler som laddas, vilket språk som är valt etc. Du kommer noga att vilja avaktivera denna inställning innan du sätter in programmet till produktion.
  • backend tillhandahåller konfiguration för I18nXHR-plugin och anger var du ska ladda översättningar från. Observera att sökvägen ska innehålla rubrikens titel, medan filen ska namnges efter namnrymden och ha .json förlängning
  • funktion (fel, t) är återuppringningen att köra när I18next är klar (eller när ett fel uppstod).

Låt oss nu göra översättningsfiler. Översättningar för ryska språket bör placeras i offentliga / i18n / ru / users.json fil:

"inloggning": "Логин"

logga in här är översättningsnyckeln, medan Логин är värdet som ska visas.

Engelska översättningar ska i sin tur gå till publika / i18n / sv / users.json fil:

"login": "Logga in"

För att se till att I18next fungerar kan du lägga till följande rad kod till återuppringning inuti i18n / config.js fil:

// config går här ... funktion (err, t) om (err) returnerar console.error (err) console.log (i18n.t ('login'))

Här använder vi en metod som heter t det betyder "översätt". Denna metod accepterar en översättningsnyckel och returnerar motsvarande värde.

Vi kan dock ha många delar av användargränssnittet som behöver översättas och gör det genom att använda t Metoden skulle vara ganska tråkigt. I stället föreslår jag att du använder ett annat plugin som heter loc-i18next som låter dig översätta flera element samtidigt.

Översätta i en go

Installera pluginet loc-i18next:

garn lägg till loc-i18next

Importera det högst upp på src / i18n / config.js fil:

importera locI18next från "loc-i18next"

Ange nu konfigurationen för själva plugin:

// andra config const loci18n = locI18next.init (i18n, selectorAttr: 'data-i18n', optionsAttr: 'data-i18n-options', useOptionsAttr: true); exportera loci18n som loci18n, i18n som i18n

Det finns ett par saker att notera här:

  • locI18next.init (i18n) skapar en ny instans av plugin baserat på den tidigare definierade förekomsten av I18next.
  • selectorAttr specificerar vilket attribut som ska användas för att upptäcka element som kräver lokalisering. I grund och botten kommer loc-i18next att söka efter sådana element och använda värdet av data i18n attribut som översättningsnyckel.
  • optionsAttr specificerar vilket attribut som innehåller ytterligare översättningsalternativ.
  • useOptionsAttr instruerar plugin att använda de extra alternativen.

Våra användare laddas asynkront, så vi måste vänta tills denna operation är klar och bara utföra lokalisering efter det. För nu, låt oss helt enkelt ställa in en timer som ska vänta i två sekunder innan du ringer till lokalisera() metod - det är en tillfällig hack, förstås.

 importera loci18n från '... / i18n / config' // annan kod ... loadUsers () hämta (this.data.get ("url")) .then (svar => response.text ()) .then (json => this.renderUsers (json) setTimeout (() => // <--- this.localize() , '2000') ) 

Koda lokalisera() metod själv:

 lokalisera () loci18n ('användare')

Som du ser behöver vi bara skicka en väljare till loc-i18next-plugin. Alla element inuti (som har data i18n attribut uppsättning) kommer att lokaliseras automatiskt.

Justera nu renderUsers metod. För nu, låt oss bara översätta "Login" -ordet:

 renderUsers (users) let content = "JSON.parse (users) .forEach ((user) => content + = '
ID: $ user.id
: $ user.login
Har laddat upp $ user.photos_count foto (er)

') this.element.innerHTML = innehåll

Trevlig! Ladda om sidan, vänta i två sekunder och se till att "Inloggning" ordet visas för varje användare.

Pluraler och kön

Vi har lokaliserad del av gränssnittet, vilket är riktigt coolt. Ändå har varje användare två ytterligare fält: antalet uppladdade foton och kön. Eftersom vi inte kan förutsäga hur många bilder varje användare kommer att ha, ska "foto" -ordet pluraliseras korrekt baserat på det angivna antalet. För att göra detta behöver vi en data-i18n-options attributet konfigurerat tidigare. För att ge räkningen, data-i18n-options ska tilldelas följande objekt: "count": YOUR_COUNT.

Kön information bör också beaktas. Ordet "uploaded" på engelska kan tillämpas på både man och kvinna, men på ryska blir det antingen "загрузил" eller "загрузила", så vi behöver data-i18n-options igen, vilket har "context": "GENDER" som ett värde. Observera förresten att du kan använda detta sammanhang för att uppnå andra uppgifter, inte bara för att ge könsinformation.

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

') this.element.innerHTML = innehåll

Uppdatera nu de engelska översättningarna:

"login": "Logga in", "uppladdat": "Har laddat upp", "foton": "ett foto", "photos_plural": "count foton"

Ingenting komplicerat här. Sedan för engelska bryr vi oss inte om könsinformationen (vilket är sammanhanget), översättningsnyckeln borde helt enkelt vara uppladdad. För att tillhandahålla korrekt pluraliserade översättningar använder vi foton och photos_plural nycklar. De räkna del är interpolering och kommer att ersättas med det faktiska numret.

Vad gäller det ryska språket är sakerna mer komplexa:

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

Först och främst notera att vi har båda uploaded_male och uploaded_female nycklar för två möjliga sammanhang. Därefter är pluraliseringsregler också mer komplexa på ryska än på engelska, så vi måste inte ge två, men tre möjliga fraser. I18next stöder många språk ur lådan, och det här lilla verktyget kan hjälpa dig att förstå vilka pluraliseringstangenter som ska anges för ett visst språk.

Växlande lokal

Vi är klara med att översätta vår applikation, men användarna ska kunna växla mellan lokaliteter. Lägg därför till en ny "språkomkopplare" -komponent till publika / index.html fil:

    Hantverk motsvarande styrenhet inuti src / styrenheter / languages_controller.js fil:

    import Controller från "stimulus" import i18n, loci18n från "... / i18n / config" export standardklass förlänger Controller initialize () let languages ​​= [title: 'engelska', kod: 'en', title: 'Русский', kod: 'ru'] this.element.innerHTML = languages.map ((lang) => return '
  • $ lang.title
  • ' ).Ansluta sig(")

    Här använder vi initialisera () återuppringning för att visa en lista över språk som stöds. Varje li har en uppgifter-action attribut som anger vilken metod (switchLanguage, i det här fallet) bör utlösas när elementet klickas på.

    Lägg nu till switchLanguage () metod:

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

    Det tar helt enkelt målet för evenemanget och tar tag i värdet av uppgifter-lang attribut.

    Jag skulle också vilja lägga till en getter och setter för currentLang attribut:

     få currentLang () return this.data.get ("currentLang") sätta currentLang (lang) if (i18n.language! == lang) i18n.changeLanguage (lang) om (this.currentLang! == lang ) this.data.set ("currentLang", lang) loci18n ("body") this.highlightCurrentLang ()

    Getteren är väldigt enkel - vi hämtar värdet av det för närvarande använda språket och returnerar det.

    Setteren är mer komplex. Först och främst använder vi ändra språk metod om det förinställda språket inte är lika med den valda. Vi lagrar också den nyligen valda platsen under dataström-lang attribut (som nås i getter), lokalisering av HTML-sidans kropp med hjälp av plugin-programmet loc-i18next och slutligen markerar den nuvarande locale.

    Låt oss koda highlightCurrentLang ():

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

    Här är vi iterating över en rad lokala växlare och jämföra värdena på deras uppgifter-lang attribut till värdet av den nuvarande locale. Om värdena matchar är omkopplaren tilldelad med a nuvarande CSS klass, annars är denna klass borttagen.

    För att göra this.switcherTargets konstruera arbete måste vi definiera Stimulus-mål på följande sätt:

    statiska mål = ["switcher"]

    Lägg också till datamålet attribut med värden på switcher för lis:

     initiera () // ... this.element.innerHTML = languages.map ((lang) => return "
  • $ lang.title
  • ' ).Ansluta sig(") //…

    En annan viktig sak att tänka på är att översättningsfiler kan ta lite tid att ladda, och vi måste vänta på att den här åtgärden ska slutföras innan du tillåter att lokalen växlas. Låt oss därför utnyttja lastad ring tillbaka:

     initiera () i18n.on ('laddad', (laddad) => // <--- let languages = [ title: 'English', code: 'en', title: 'Русский', code: 'ru' ] this.element.innerHTML = languages.map((lang) =>  lämna tillbaka '
  • $ lang.title
  • '). gå med (") this.currentLang = i18n.language)

    Slutligen glöm inte att ta bort setTimeout från loadUsers () metod:

     loadUsers () hämta (this.data.get ("url")) .then (svar => response.text ()) .then (json => this.renderUsers (json) this.localize ())

    Persistent språk i URL-adressen

    Efter att bytet har ändrats skulle jag vilja lägga till en ?lang GET-parameter till webbadressen som innehåller koden för det valda språket. Lägga till en GET param utan att ladda om sidan kan enkelt göras med hjälp av 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()  

    Upptäcka Locale

    Det sista vi kommer att genomföra idag är möjligheten att ställa in lokalen baserat på användarens preferenser. Ett plugin som heter LanguageDetector kan hjälpa oss att lösa den här uppgiften. Lägg till ett nytt garnpaket:

    garn lägg till i18next-browser-languagedetector

    Importera LanguageDetector inuti i18n / config.js fil:

    importera LngDetector från "i18next-browser-languagedetector"

    Justera nu konfigurationen:

    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 ordning alternativet listar alla tekniker (sorterade efter deras betydelse) att plugin ska försöka för att "gissa" det föredragna läget:

    • frågesträng betyder att man kontrollerar en GET param som innehåller lokalens kod.
    • lookupQuerystring anger namnet på GET param att använda, vilket är lang i vårat fall.
    • navigatör betyder att man får lokaldata från användarens begäran.
    • htmlTag innebär att man hämtar den föredragna platsen från lang attribut av html märka.

    Slutsats

    I den här artikeln har vi tittat på I18next-en populär lösning för att enkelt översätta JavaScript-applikationer. Du har lärt dig hur du integrerar I18next med Stimulus-ramen, konfigurerar den och lägger översättningsfiler på asynkront sätt. Du har också sett hur du växlar mellan lokala och ställer in standardspråket baserat på användarens inställningar.

    I18next har några ytterligare konfigurationsalternativ och många plugins, så var säker på att bläddra i sin officiella dokumentation för att lära dig mer. Observera också att Stimulus inte tvingar dig att använda en specifik lokaliseringslösning, så du kan också försöka använda något som jQuery.I18n eller Polyglot. 

    Det är allt för idag! Tack för att du läste med, och till nästa gång.