Как стать автором
Обновить
2092.48

Nuxt.js app от UI-кита до деплоя. Часть 3: Мультиязычность

Время на прочтение7 мин
Количество просмотров4.2K
Привет!

Это третья часть цикла статей о создании современного блога на Nuxt.js. Сегодня реализуем мультиязычность в приложении, которое мы написали в первой и второй частях.



Обратите внимание, что код каждой части можно найти в собственной ветке на Github, а в master доступна версия приложения из последней опубликованной статьи.

Зачем нужен мультиязычный сайт?


Преимущества будут различны и зависят от сферы вашего бизнеса, конкретного продукта или услуги. Однако приведем самые значимые, на наш взгляд, плюсы, которые может дать мультиязычный сайт:

  • привлечение к продукту более широкой аудитории,
  • уменьшение отказов и повышение конверсии,
  • установление конкурентного преимущества,
  • международная поисковая оптимизация,
  • развитие бренда.

Многоязычный веб-сайт может изменить бизнес и помочь продукту стать более конкурентоспособным на мировом рынке.

@nuxtjs/i18n


Для реализации мультиязычности мы будем использовать модуль @nuxtjs/i18n, который предоставляет нам следующие возможности:

  • автоматическая генерация маршрутов и создание настраиваемых путей,
  • поисковая оптимизация,
  • “ленивая” загрузка переводов для отдельных языков,
  • автоматическое определение языка браузера пользователя и перенаправление на нужную версию сайта,
  • разные доменные имена для разных языков (в данном приложении мы не будем это использовать),
  • сохранение текущей локали и переводов с помощью Vuex (в данном приложении мы не будем это использовать).

Для начала установим модуль:

npm i nuxt-i18n

Теперь создадим в корне проекта директорию locales. Эта директория будет содержать файл конфигурации nuxt-i18n, а также файлы с переводами.

Создадим файл конфигурации i18n-nuxt-config.js со следующим содержимым:

export const i18n = {
  locales: [
    {
      code: 'en',
      iso: 'en-US',
      name: 'EN',
      file: 'en.js',
    },
    {
      code: 'ru',
      iso: 'ru-RU',
      name: 'RU',
      file: 'ru.js',
    },
  ],
  lazy: true,
  langDir: '/locales/',
  defaultLocale: 'en',
  detectBrowserLanguage: {
    alwaysRedirect: true,
    fallbackLocale: 'en',
    onlyOnRoot: true,
  },
}

Здесь мы указываем ряд опций, которые должны быть переданы в явном виде или отличаться от значений по умолчанию. Полный список доступных опций можно найти по ссылке.

Давайте рассмотрим некоторые из них поподробнее:

1. locales — список языков, которые будут реализованы в нашем приложении. Как вы видите, в качестве второго языка был выбран русский (RU), но можно также указать любой другой язык или несколько языков. Обратите внимание, что в каждом элементе массива указано свойство file. Это обязательное свойство, если мы хотим использовать “ленивую” загрузку переводов.

2. lazy — признак, что мы хотим использовать “ленивую” загрузку переводов. В этом случае после открытия пользователем нашего приложения он скачает с сервера только файл с переводом для установленного у него языка, что положительно скажется на скорости загрузки сайта.

3. langDir — эта директория будет использоваться как корневая при поиске файлов с переводами.

4. defaultLocale — локаль, которая будет использоваться по умолчанию. Для этого языка в строке URL не будет использован префикс, а для других — будет.

То есть в нашем случае для английского языка адрес в браузере будет выглядеть следующим образом: https://nuxt-blog.hostman.site, а для русского так: https://nuxt-blog.hostman.site/ru.

5. detectBrowserLanguage — с помощью этих опций мы говорим приложению использовать при первом заходе пользователя в приложение язык, установленный в браузер, чтобы отобразить сайт на нужном языке, а если этот язык в приложении не реализован, то открыть английскую версию.

Отлично! Мы разобрались с опциями и готовы двигаться дальше.

Нам нужно создать файлы en.js и ru.js, которые будут содержать переводы на эти языки. Файлы имеют одинаковую структуру и отличаются только переводом, поэтому достаточно рассмотреть подробно один из них. Например, en.js:

export default {
  home: {
    title: 'Nuxt blog',
    subtitle: 'The best blog you can find on the global internet',
    postCount: 'Total posts: ',
  },
  post: {
    linkHome: 'Home',
  },
  footer: {
    feedback: 'Contact us',
    copyright: 'All rights reserved',
  },
}

Как можно заметить, здесь мы сгруппировали переводы по логическим группам, что позволит в дальнейшем удобно обращаться к нужным свойствам.

В нашем случае используем всего один файл для одного языка, но для больших приложений удобно создавать отдельную директорию для каждого языка и разделять перевод на несколько файлов по определенному принципу. Например, одна страница — один файл, объединяя затем все эти файлы в один объект с помощью стандартного механизма импортов.

Итоговая структура директории locales должна иметь следующий вид:

locales/
-- i18n-nuxt-config.js
-- en.js
-- ru.js

Директория locales на Github.

Всё, что нам остаётся сделать, — добавить информацию об этом модуле в секцию modules в файле nuxt.config.js:

{
  modules: [
    ['nuxt-i18n', i18n],
  ]
}

Не забудьте добавить в начало файла импорт переменной i18n, которую мы использовали выше, с нашей конфигурации:

import { i18n } from './locales/i18n-nuxt-config'

Использование в компонентах


Мы подготовили переводы для разных языков, и теперь самое время начать использовать их в наших компонентах.

Модуль @nuxtjs/i18n добавляет к экземпляру Vue несколько функций (будут доступны из контекста this). Мы будем использовать следующие:

1. $t(<key>) — с помощью этой функции мы можем получить доступ к любому свойству перевода, указанному в наших файлах en и ru. Например, $t('home.title').

2. localePath(<path>, ...) — возвращает локализованный URL-адрес для запрашиваемой страницы. Первым параметром может быть либо путь, либо имя маршрута, либо объект для более сложных маршрутов. Узнать подробнее об этой функции и посмотреть примеры её использования можно здесь.

Поскольку использование во всех компонентах будет однотипное, то для примера рассмотрим компонент LinkToHome, а остальные можно рассмотреть самостоятельно на Github.

Шаблон компонента LinkToHome после изменений будет выглядеть следующим образом:

<template lang="pug">
section.section
  .content
    nuxt-link.lth__link(
      class="primary"
      :to="localePath('/')"
    )
      img.lth__link-icon(
        src="~/assets/icons/home.svg"
        alt="icon-home"
      )
      | {{ $t('post.linkHome') }}
</template>

Мы заменили прямое указание маршрута в nuxt-link на вызов функции localePath, передав в качестве параметра нужный нам маршрут. Точно так же прямое указание названия ссылки мы заменили на вызов функции $t, передав параметром нужное нам свойство.

→ Компонент LinkToHome на Github

Также изменения были выполнены в компонентах DbFooter, PostCard, PostList, HomePage.

Посты на разных языках


Вы, наверное, обратили внимание, что в файлах en.js и ru.js не было никакой информации о постах, которые мы храним в отдельных файлах в директории content/posts. Настало время поговорить об этом!

До этого в файле posts.ts мы хранили и экспортировали информацию о наших постах следующим образом:

export default [  
  {
    id: 1,  
    title: 'Post 1',  
    desc:  
      'A short description of the post to keep the user interested.' +  
      ' Description can be of different lengths, blocks are aligned' +  
      ' to the height of the block with the longest description',  
    file: 'content/posts/1.md',
    img: 'assets/images/1.svg',
  },

  ...

] as Post[]

Но в таком варианте мы не можем указывать разные файлы и метаинформацию для одного поста на разных языках. Исправим это и приведём наш файл к следующему виду:

const en = [  
  {
    id: 1,  
    title: 'Post 1',  
    desc:  
      'A short description of the post to keep the user interested.' +  
      ' Description can be of different lengths, blocks are aligned' +  
      ' to the height of the block with the longest description',  
    file: 'content/posts/1.md',
    img: 'assets/images/1.svg',
  },

  ...

] as Post[]

const ru = [  
  {
    id: 1,
    title: 'Пост 1',
    desc:
      'Краткое описание поста, чтобы заинтересовать пользователя.' +
      ' Описание может быть разной длины, блоки выровнены' +
      ' по высоте блока с самым длинным описанием',
    file: 'content/posts/1.md',
    img: 'assets/images/1.svg',
  },

  ...

] as Post[]

export default { en, ru }

Теперь у нас есть две переменных en и ru, каждая из которых хранит собственный список постов для своего языка.

→ Файл на Github

В данном случае для обоих языков будут использованы одни и те же файлы, так как это демонстрационное приложение, и здесь мы хотим показать сам подход к реализации. В реальном приложении файлы, конечно, должны быть разные.

Давайте теперь немного изменим реализацию компонентов, где используется эта информация.

Компонент PostList


В компоненте PostList требуется заменить свойство posts объекта data на вычисляемое свойство с таким же названием, которое будет выглядеть следующим образом:

 computed: {
    posts(): Post[] {
      const { locale } = (this as any).$i18n

      return posts[locale as 'en' | 'ru']
    },
  },

→ Компонент на Github

Отдельная страница поста post/_id


На отдельной странице поста реализовано вычисляемое свойство currentPost, давайте немного изменим его, чтобы оно выглядело следующим образом:

 computed: {
    currentPost(): Post | undefined {
      const { locale } = (this as any).$i18n

      return posts[locale as 'en' | 'ru'].find(
        (post: Post) => post.id === this.currentId
      )
    },
  },

→ Файл на Github

Теперь наши посты на всех страницах будут корректно загружаться в зависимости от выбранного языка.

В данном случае мы не учитываем сценарий, когда пост с конкретным id существует только на одном языке, так как наше приложение демонстрационное и мы можем этим пренебречь. Не забудьте учесть этот сценарий, если будете реализовывать подобную логику в реальном приложении.

Переключатель языка


На этом этапе реализуем переключатель, который позволит выбирать нужный нам язык на сайте.

В прошлой части мы создали компонент AppOptions, который служит обёрткой для переключателей темы и языка.

Давайте создадим компонент SwitcherLang в директории components/AppOptions и приведём шаблон компонента AppOptions к следующему виду:

<template lang="pug">
section.section
  .content
    .app-options
      switcher-lang
      switcher-color-mode
</template>

Взглянем на шаблон компонента SwitcherLang:

<template lang="pug">
select(
  v-model="selected"
  class="body3 medium"
  @change="changeLocale"
)
  option(
    v-for="locale in $i18n.locales"
    :key="locale.code"
    :value="locale.code"
  ) {{ locale.name }}
</template>

Как вы видите, мы просто реализуем стандартный select, в котором показываем доступные в приложении языки. При изменении значения в этом select мы будем вызывать метод changeLocale.

Далее посмотрим на секцию script:

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'SwitcherLang',

  data: () => ({
    selected: '' as 'en' | 'ru',
  }),

  created() {
    this.selected = (this as any).$i18n.locale
  },

  methods: {
    changeLocale() {
      const switchPath = (this as any).switchLocalePath(this.selected)
      this.$router.push(switchPath)
    },
  },
})
</script>

В методе changeLocale мы используем ещё одну функцию, которую предоставляет нам модуль @nuxt/i18n, — switchLocalePath. Эта функция возвращает ссылку на текущую страницу на выбранном языке.

Также обратите внимание, что мы храним значение выбранного языка в локальной переменной selected и при создании экземпляра Vue в хуке created синхронизируем её значение с глобальным значением, которое хранится в $i18n.

→ Компонент на Github

Заключение


Что у нас получилось:


Пишите в комментариях, если будут вопросы или непонятные моменты. До встречи в следующей части!
Теги:
Хабы:
+5
Комментарии0

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud