Ako naučiť Vue.js a Nuxt aplikáciu po slovensky


Ako naučiť Vue.js a Nuxt aplikáciu po slovensky

Keď sme s Vue.js či s Nuxt začali pracovať, bola práve zmena jazyka a celkovo lokalizácia aplikácie veľkým strašiakom, keďže sme vedeli, koľko úsilia nás to stálo pri počiatkoch nášho projektu RSHOP. Bola to pre nás veľká neznáma, ale vďaka skúsenostiam na rôznych multijazykových projektoch sme tušili, čo približne nás čaká.

Preklad textov je síce to najprácnejšie, ale zároveň to najjednoduchšie, čo developera pri lokalizácii aplikácie čaká. Zmenou jazyka či krajiny prichádzajú ďalšie výzvy v podobe zmeny URL odkazov, formátu dátumov, meny cien produktov, ale aj také bonusy v podobe premenných v textoch, zobrazenie textov podľa určitého množstva a ďalšie podobné srandy, ktoré si developer uvedomí až pri programovaní.

Čo teraz? Programovať všetko odznovu by stálo veľa úsilia, priveľa nervov, možno trochu sĺz, a navyše, toto koleso sa už dávno vymyslelo. Preto sme vyhodnocovali možnosti a v prípade Vue.js pre lokalizáciu je naším favoritom plugin Vue i18n, prípadne pre Nuxt je dostupná jeho implementácia v podobe pluginu nuxt-i18n. Na prvý pohľad ide o jednoduchý plugin, ale jeho autori mysleli na všetky drobnosti, na ktoré pri rôznych jazykových mutáciách natrafíš.

V tomto článku ukážem, ako nakonfigurovať plugin, aké má features a spomeniem aj pár bonusových features či knižníc, ktoré ti pri lokalizácii uľahčia život. Budem ukazovať implementáciu pre Vue.js aj Nuxt aplikáciu, aby si videl prípadné rozdiely, ale keďže ide v podstate o jeden plugin, základná funkcionalita je rovnaká.

Inštalácia

K inštalácii asi netreba povedať nič. Jediné, na čo nesmieš zabudnúť je použitie pluginu pre Vue.js, prípadne Nuxt aplikáciu.

Vue.js

npm install vue-i18n
// app.js
 
import Vue from 'vue'
import VueI18n from 'vue-i18n'
 
Vue.use(VueI18n)

Nuxt

npm install nuxt-i18n
// nuxt.config.js
 
{
  modules: [
    [
      'nuxt-i18n'
    ]
  ],
 
  i18n: {}
}

Na začiatok definujem, v akých jazykoch chcem, aby aplikácia dokázala komunikovať. Ide o, samozrejme, slovenčinu, ale pre testovanie dáme aj angličtinu a napríklad nemčinu. Definujem teda jednotlivé locales, texty s prekladmi, a asi by bolo vhodné určiť default jazyk.

Vue.js

// app.js
 
const messages = {
  en: {
    message: {
      hello: 'hello world'
    }
  },
  de: {
    message: {
      hello: 'hallo welt'
    }
  },
  sk: {
    message: {
      hello: 'lorem ipsum'
    }
  }
}
 
const i18n = new VueI18n({
  locale: sk, 
  messages
})
 
new Vue({ i18n }).$mount('#app')

Nuxt

// nuxt.config.js
 
{
  modules: [
    'nuxt-i18n'
  ],
 
  i18n: {
    locales: ['en', 'de', 'sk'],
    defaultLocale: 'sk',
    vueI18n: {
      messages: {
        en: {
	  message: {
            hello: 'hello world'
	  }
        },
        de: {
	  message: {
            hello: 'hallo welt'
	  }
        },
        sk: {
	  message: {
            hello: 'lorem ipsum'
	  }
        }
      }
    }
  }
}

Teraz mám ako default jazyk nastavenú slovenčinu, takže ak vypíšem text určený na preklad nasledovným spôsobom:

<div id="app">
  <p>{{ $t("message.hello") }}</p>
</div>

Na výstupe dostanem preložený text, podľa kľúča:

<div id="app">
  <p>lorem ipsum</p>
</div>

Keď sme s i18n pluginom začínali, miesto kľúča textu sme dávali klasický text v SK jazyku, ale potom sme prešli ku kľúčovému zápisu, pri ktorom vieme jednoducho zmeniť napríklad text na stránke, bez nutnosti zásahu do kódu.

Textov skrz celú aplikáciu bude asi veľa, nedáva teda celkom zmysel udržiavať ich všetky v jednom súbore. Preto sa pre každý jeden jazyk vytvorí JSON súbor, v ktorom sa budú udržiavať texty pre každý jazyk samostatne.

Vue.js

// app.js
 
import VueI18n from "vue-i18n"
import en from './locales/en.json'
import de from './locales/de.json'
import sk from './locales/sk.json'
 
const messages = {
  en,
  de,
  sk
}
 
const i18n = new VueI18n({
  locale: 'sk',
  messages
})

Nuxt

// nuxt.config.js
 
...
i18n: {
  locales: ['en', 'de', 'sk'],
  defaultLocale: 'sk',
  vueI18n: {
    fallbackLocale: 'sk',
    messages: {
      sk: require('./locales/sk.json'),
      en: require('./locales/en.json'),
      de: require('./locales/de.json')
    }
  }
}

Texty

Keď už je v kóde poriadok, poďme sa pozrieť na pár chuťoviek, ktoré nás pri prekladaných textoch čakajú.

Premenná v texte
Premenné v texte môžeme označovať napríklad podľa poradia v poli, alebo ešte lepšie, podľa kľúča objektu.

const messages = {
  en: {
    message: {
      hello: '{msg} world'
    }
  }
}
<p>{{ $t('message.hello', { msg: 'hello' }) }}</p>

Pluralizácia
Pri pluralizácii textu je potrebné myslieť na to, že každý jazyk má vlastnú formu pluralizácie. Plugin je na to pripravený a jednoduchou konfiguráciou v podobe extend funkcie si pluralizáciu pripravíme pre slovenčinu.

// nuxt.config.js
 
...
i18n: {
  vueI18n: {
    pluralizationRules: {
      'sk': (choice) => {
        if (choice === 0) {
          return 2
        }
 
        if (choice === 1) {
          return 0
        }
 
        if (choice < 5) {
          return 1
        }
 
        return 2
      }
    }
  }
}
...
const messages = {
  sk: {
    message: {
      plural: '{count} kus | {count} kusy | {count} kusov'
    }
  }
}
<p>{{ $tc('message.plural', 10, { count: 10 }) }}</p>

HTML kód v texte
Je viacero možností, ako pracovať s HTML v textoch, ale najviac nám, aspoň zatiaľ, vyhovovalo použitie slots.

const messages = {
  sk: {
    message: {
      html: 'Lorem ipsum{0}dolor sit amet,{1}consectetur adipiscing elit.{link}',
      link: 'Text'
    }
  }
}
<i18n
  path="message.html"
  tag="p"
>
  <template #0>
    <br />
  </template>
 
  <template #1>
    <br />
  </template>
 
  <template #link>
    <a href="#" :title="$t('message.link')">{{ $t('message.link') }}</a>
  </template>
</i18n>

Na výstupe potom dostanem HTML:

<p>
  Lorem ipsum<br />
  dolor sit amet,<br />
  consectetur adipiscing elit.
  <a href="#" title="Text">Text</a>
</p>

Zmena jazyka pomocou komponentu

Vyššie v článku som ukázal, ako priamo definovať primárny jazyk. Ak chcem, aby mal užívateľ možnosť zmeniť jazyk aplikácie, môžem vytvoriť jednoduchý komponent jeho zmeny.

Vue.js

<template>
  <div class="languages">
    <select v-model="$i18n.locale">
      <option v-for="(locale, i) in langs" :key="`lang-${i}`" :value="locale">{{ locale }}</option>
    </select>
  </div>
</template>
 
<script>
export default {
  name: 'LanguageSwitcher',
  data ()  {
    return { langs: ['sk', 'en', 'de'] }
  }
}
</script>

Nuxt

// components/LanguageSwitcher.vue
 
<template>
<div class="languages">
  <nuxt-link
    v-for="locale in langs"
    :key="locale.code"
    :to="switchLocalePath(locale.code)">{{ locale.name }}</nuxt-link>
</div>
</template>
 
<script>
export default {
  computed: {
    langs () {
      return this.$i18n.locales.filter(i => i.code !== this.$i18n.locale)
    }
  }
}
</script>
// nuxt.config.js
 
...
/*
 ** Nuxt.js modules
 */
modules: [
  ['nuxt-i18n', {
    locales: [
      {
        code: 'sk',
        name: 'Slovenčina'
      },
      {
        code: 'en',
        name: 'English'
      },
      {
        code: 'de',
        name: 'Deutsch'
      }
    ]
  }]
]
...

Definovanie jazyka podľa browsera, domény alebo prefixu v URL

Neviem, či tieto možnosti ponúka aj priamo Vue.js verzia, ale v Nuxt verzii ide len o jednoduchú konfiguráciu.

By default plugin sa snaží užívateľov nasmerovať na jazykovú verziu podľa nastaveného jazyka browsera. To znamená, že keď príde užívateľ s browserom nastaveným na anglický jazyk na slovenskú verziu stránky, plugin pri prvom vstupe na stránku užívateľa presmeruje na jej anglickú verziu, napríklad „domain.dev/en“, nastaví cookie a pri ďalšej návšteve aplikácie bude podľa nej vyhodnocovať, aký jazyk aplikácii nastaviť. Všetko je, samozrejme, konfigurovateľné. Napríklad môžem zmeniť názov cookie, zrušiť presmerovania alebo zrušiť detekciu jazyka podľa browsera úplne.

// nuxt.config.js
 
...
/*
 ** Nuxt.js modules
 */
modules: [
  ['nuxt-i18n', {
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'my_custom_cookie_name',
      alwaysRedirect: true
    }
  }]
]
...
// nuxt.config.js
 
...
/*
 ** Nuxt.js modules
 */
modules: [
  ['nuxt-i18n', {
    detectBrowserLanguage: false
  }]
]
...

V prípade, že chcem jednotlivé jazykové verzie mať na rôznych doménach, jediné, čo potrebujem je ich definovanie v konfigurácii.

// nuxt.config.js
 
...
/*
 ** Nuxt.js modules
 */
modules: [
  ['nuxt-i18n', {
    locales: [
      {
        code: 'sk',
        domain: 'domain.dev'
      },   
      {
        code: 'en',
        domain:'en.domain.dev'
      },
      {
        code: 'de',
        domain: 'de.domain.dev'
      }
    ],
    differentDomains: (process.env.NODE_ENV === 'production')
  }]
]
...

Prípadne je možnosť nastavenia jazyka podľa prefixu, ktorý sa pridá pred určitú route. Napríklad pri registrácii by bol tvar URL „domain.dev/en/registration“.

// nuxt.config.js
 
...
i18n: {
  strategy: 'prefix'
}

Pre strategy option máme 4 možnosti:
no_prefix: do URL sa žiadny prefix nepridá a jazyk sa vyhodnocuje napríklad len podľa browsera, nastavenej cookie…
prefix: všetky jazyky budú mať v URL prefix,
prefix_except_default: všetky jazyky, okrem definovaného default jazyka, budú mať v URL prefix. To znamená, že ak mám default slovenskú verziu stránky, plugin nebude pridávať do URL /sk, ale pre ostatné jazyky sa bude správať ako „prefix“ možnosť,
prefix_and_default: všetky jazyky budú mať v URL prefix, ale defaultný jazyk bude dostupný aj vo verzii bez prefixu.

Fallback jazyk

V prípade, že vybraný jazyk nemá pre nejaký text definovaný preklad, nie je asi žiadúce zobraziť iba kľúč textu. Plugin má pre tento prípad možnosť zadefinovania fallback jazyka, ktorý sa bude zobrazovať pre texty, ktorým chýba preklad.

// nuxt.config.js
 
...
i18n: {
  vueI18n: {
    locale: 'sk',
    fallbackLocale: 'en',
    messages
  }
}
...

Ďalším prípadom môže byť, že pre niektorý jazyk chcem použiť množinu fallback jazykov, prípadne potrebujem vyhodnocovať fallback pre každý jazyk samostatne.

// nuxt.config.js
 
...
i18n: {
  vueI18n: {
    fallbackLocale: {
      'sk': ['en', 'de'],
      'en': ['de'],
      'de': ['en']
    }
  }
}
...

URL odkazy

„Pekné“ odkazy sú často takým príjemným bonusom, ktorý si veľa užívateľov možno ani nevšimne, ale developer s nimi zabije niekedy až priveľa času. Aj keď teraz budem písať len o Nuxt verzii, určite nájdeš na internete veľa možností, ako na odkazy vo Vue.js verzii.

Nuxt plugin má veľmi jednoduchú možnosť konfigurovania odkazov pre jednotlivé jazyky priamo v komponente stránky, takže nemusíš hľadať žiadny config na jednotlivé routes.

// pages/registration.vue
 
export default {
  nuxtI18n: {
    paths: {
      en: '/registration',
      de: '/registration'
      sk: '/registracia'
    }
  }
}

V prípade, že som na slovenskej verzii aplikácie a zadám si „/registration“, dostanem 404, keďže je stránka dostupná iba pre inú jazykovú verziu.

Ak ti tento spôsob zápisu odkazov nevyhovuje, všetky môžeš definovať v nuxt.config.js.

V template sa tieto odkazy vytvárajú cez komponent nuxt-link, pomocou metódy localePath, ktorej ako parameter treba dať len cestu k danej stránke.

Napríklad pre stránku registrácia „pages/registration.vue“:

<nuxt-link :to="localePath('registration')">Link</nuxt-link>

V prípade, že sa odkaz nachádza v subzložke „pages/user/registration.vue“, jeho cestu je potrebné zapísať dashed zápisom:

<nuxt-link :to="localePath('user-registration')">Link</nuxt-link>

Obmedzenie stránky pre konkrétne jazyky

Ak je potrebné mať nejakú stránku dostupnú len pre určité jazyky, v Nuxt priamo v komponente je možné tieto jazyky povoliť.

// pages/about.vue
 
export default {
  nuxtI18n: {
    locales: ['sk']
  }
}

Toto je využiteľné pre stránky, ktoré ešte nemajú pripravené preklady alebo nemajú byť pre iné jazyky vôbec dostupné.

Single File Components

Pre lepší prehľad, ale aj pre optimalizovanie načítaných textov je možné použiť nový blok „i18n“, priamo v SFC. V tomto bloku sa JSON zápisom vkladajú jednotlivé preklady.

<i18n>
{
  en: {
    message: {
      hello: 'hello world'
    }
  },
  de: {
    message: {
      hello: 'hallo welt'
    }
  },
  sk: {
    message: {
      hello: 'lorem ipsum'
    }
  }
}
</i18n>

Týchto blokov môže byť v rámci jedného SFC definovaných viacero, čo sa hodí napríklad pri definovaní viacerých jazykov v samostatnom bloku.

<i18n>
{
  en: {
    message: {
      hello: 'hello world'
    }
  }
}
</i18n>
 
<i18n>
{
  de: {
    message: {
      hello: 'hallo welt'
    }
  }
}
</i18n>
 
<i18n>
{
  sk: {
    message: {
      hello: 'lorem ipsum'
    }
  }
}
</i18n>

Preklady je možné tiež načítať z externého JSON súboru.

<i18n src="./locales/sk.json"></i18n>

Hlavný rozdiel pri Vue.js a Nuxt verzii pluginu je, že Nuxt túto možnosť má priamo v jadre a stačí ju len povoliť.

// nuxt.config.js
 
['nuxt-i18n', {
  vueI18nLoader: true
}]

Pri Vue.js verzii je potrebná inštalácia nového loadera pre webpack.

npm install --save-dev @kazupon/vue-i18n-loader
// webpack.js
 
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        resourceQuery: /blockType=i18n/,
        type: 'javascript/auto',
        loader: '@kazupon/vue-i18n-loader'
      }
      // ...
    ]
  },
  // ...
}

Ak používaš Vue CLI, konfigurácia CLI je nasledovná:

// vue.config.js
 
module.exports = {
  chainWebpack: config => {
    config.module
      .rule("i18n")
      .resourceQuery(/blockType=i18n/)
      .type('javascript/auto')
      .use("i18n")
        .loader("@kazupon/vue-i18n-loader")
        .end();
  }
}

Dátumy

Na prácu a formátovanie dátumov používame knižnicu Moment.js, ktorý má na rozdiel od pluginu Vue i18n oveľa rozsiahlejšie možnosti nielen na formátovanie dátumov, ale aj ich manipuláciu, a je veľmi jednoduchý na používanie.

Inštalácia knižnice:

npm install moment --save

Inštalácia Nuxt verzie Moment.js:

npm install @nuxtjs/moment --save
export default {
  buildModules: [
    '@nuxtjs/moment'
  ]
}

Priamo pre Nuxt verziu sme potom vytvorili vlastný plugin na SK lokalizáciu.

// plugins/moment.js
 
export default ({ $moment }) => {
  $moment.defineLocale('sk', {
    months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Máj', 'Jún', 'Júl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
    weekdays: ['Nedeľa', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota'],
    weekdaysShort: ['Ned', 'Pon', 'Ut', 'Str', 'Štv', 'Pia', 'So'],
    weekdaysMin: ['N', 'P', 'U', 'S', 'Š', 'P', 'S'],
    week: {
      dow: 1
    }
  });
}

Teraz je možné použiť knižnicu priamo vo Vue template:

<div>{{ $moment().format('DD.MM.YYYY') }}</div>

Lazy loading

Pre aplikácie s veľkým množstvom textov je určite vhodné texty nepribaliť priamo do vygenerovaných bundlov. Z dôvodu optimalizácie je vhodnejším riešením lazy loading týchto textov, pre zobrazenú jazykovú verziu aplikácie. Lazy loading má opäť Nuxt verzia, na rozdiel od verzie základnej, implementovaný v jadre a jeho povolenie pozostáva zo 4 krokov:

  • Nastavenie konfigurácie „lazy“ na hodnotu „true“.
  • Definovanie zložky, v ktorej sú uložené preklady konfiguráciou „langDir“. Táto hodnota nesmie byť prázdna a definuje, odkiaľ sa potom budú preklady dynamicky importovať.
  • Definovanie „locales“ a ich súborov obsahujúcich preklady.
  • Každý súbor s prekladmi musí byť JS súbor a môže vrátiť objekt alebo funkciu (Promise). Nie je možné nastaviť lazy loading na JSON súbory.
// nuxt.config.js
 
['nuxt-i18n', {
  locales: [
    {
      code: 'en',
      file: 'en.js'
    },
    {
      code: 'de',
      file: 'de.js'
    },
    {
      code: 'sk',
      file: 'sk.js'
    }
  ],
  lazy: true,
  langDir: 'locales/'
}]
// locales/[lang].js
 
export default async (context, locale) => {
  await resolve({
    hello: 'Hello World'
  })
}
 
// alebo
 
export default {
  hello: 'Hello World'
}

Vue i18n plugin ponúka toho ešte viac než som stihol v tomto článku spomenúť, ako napríklad formátovanie mien pre ceny, aktualizovanie SEO metadát a veľa ďalších možností, ktoré sa dočítaš pri študovaní oficiálnej dokumentácie. Verím ale, že som všetko podstatné spomenul a článok ti dal minimálne prehľad, ako k problematike lokalizácie Vue.js aplikácie pristupovať. Ak by si mal nejaké otázky, pripomienky, prípadne používaš iné knižnice či pluginy, určite sa nám neváhaj ozvať.

Ak máš záujem riešiť Vue.js aj ty, prečítaj si naše predošlé články zo série:

Event Bus

Riešime Vue.js #3

Nuxt aplikácia v kontajneroch

Riešime Vue.js #1

Testovanie Vue.js komponentov

vue.js
axios
moxios
testing

V prípade, že by si nám chcel pomôcť s vývojom ďalšej generácie nášho projektu RSHOP, alebo máš záujem sa od nás niečomu priučiť, neváhaj a ozvi sa nám. Ak náhodou na našej kariérnej stránke kariera.riesenia.com nenájdeš svoju vysnívanú poziciu, napíš nám priamo na e-mail joinus@riesenia.com a radi ťa u nás privítame.

+ Diskusia nemá žiadne príspevky