Поскольку Vue.js — это доступная, производительная и универсальная платформа для создания пользовательских веб-интерфейсов, для нее также требуется лучшее в своем классе решение для интернационализации.
Возможно, вы знаете vue-i18n, но для тех, кто уже знает i18next адаптированная для Vue.js версия i18next была бы более подходящей.

В этом уроке мы будем использовать модуль i18next-vue.

Итак, прежде всего: «Почему i18next?»

Что касается локализации React. Одним из самых популярных является i18next с его расширением Vue i18next-vue, и на то есть веские причины:

i18next был создан в конце 2011 года. Он старше, чем большинство библиотек, которые вы будете использовать в настоящее время, включая вашу основную технологию интерфейса (React, Angular, Vue,…).

➡️ экологичный

Исходя из того, как долго i18next уже доступен с открытым исходным кодом, не существует реального дела i18n, которое нельзя было бы решить с помощью i18next.

➡️ зрелый

i18next можно использовать в любой среде javascript (и некоторых не-javascript — .net, elm, iOS, android, ruby, …), с любой структурой пользовательского интерфейса, с любым форматом i18n, … возможности бесконечный.

➡️ расширяемый

С i18next вы получите множество функций и возможностей по сравнению с другими обычными платформами 18n.

➡️ богатый

Здесь вы можете найти больше информации о том, почему i18next особенный и как он работает.

Давайте углубимся в это…

Предпосылки

Убедитесь, что у вас установлены Node.js и npm. Лучше всего, если у вас есть некоторый опыт работы с простым HTML, JavaScript и базовым Vue.js, прежде чем переходить к i18next-vue.

Начиная

Возьмите свой собственный проект Vue или создайте новый, новый, т.е. с помощью команды vue create cli.

npx @vue/cli create vue-starter-project

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

Давайте установим некоторые зависимости i18next:

npm install i18next i18next-vue i18next-browser-languagedetector

Подготовим файл i18n.js:

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    resources: {
      en: {
        translation: {
          // here we will place our translations...
        }
      }
    }
  });

export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

Давайте импортируем этот файл в наш файл main.js:

import { createApp } from 'vue'
import i18n from './i18n'
import App from './App.vue'

i18n(createApp(App)).mount('#app')

Теперь давайте попробуем перенести какой-нибудь жестко закодированный текст в переводы.

Для первого текста мы просто используем простую клавишу welcome для прямого вызова функции $t. $t более или менее совпадает с i18next.t.

Для второго текста мы будем использовать директиву v-html для прямого вывода реального HTML.

Предупреждение системы безопасности
Динамическое отображение произвольного HTML-кода на вашем веб-сайте может быть очень опасным, поскольку может легко привести к XSS-уязвимостям. Используйте v-html только для надежного контента и никогда для контента, предоставленного пользователями.

<template>
  <div class="hello">
    <h1>{{ $t('welcome') }}</h1>
    <p v-html="$t('descr')"></p>
  </div>
</template>

<script>
export default {
  name: 'TranslationShowCase'
}
</script>

Тексты теперь являются частью ресурсов перевода:

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    resources: {
      en: {
        translation: {
          welcome: 'Welcome to Your Vue.js App',
          descr: 'For a guide and recipes on how to configure / customize '
            + 'this project,<br>check out the '
            + '<a href="https://cli.vuejs.org" target="_blank" '
            + 'rel="noopener">vue-cli documentation</a>.'
        }
      }
    }
  });

export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

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

Теперь давайте определим переключатель языка:

<template>
  <div class="hello">
    <h1>{{ $t('welcome') }}</h1>
    <p v-html="$t('descr')"></p>
    <hr />
    <div>
      <div v-if="languages">
        <span v-for="(lng, index) in Object.keys(languages)" :key="lng">
          <a v-if="$i18next.resolvedLanguage !== lng" v-on:click="$i18next.changeLanguage(lng)">
            {{ languages[lng].nativeName }}
          </a>
          <strong v-if="$i18next.resolvedLanguage === lng">
            {{ languages[lng].nativeName }}
          </strong>
          <span v-if="index < (Object.keys(languages).length - 1)">&nbsp;|&nbsp;</span>
        </span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TranslationShowCase',
  data () {
    return {
      languages: {
        en: { nativeName: 'English' },
        de: { nativeName: 'Deutsch' }
      }
    }
  }
}
</script>

А также добавить несколько переводов для нового языка:

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    resources: {
      en: {
        translation: {
          welcome: 'Welcome to Your Vue.js App',
          descr: 'For a guide and recipes on how to configure / customize '
            + 'this project,<br>check out the '
            + '<a href="https://cli.vuejs.org" target="_blank" '
            + 'rel="noopener">vue-cli documentation</a>.'
        }
      },
      de: {
        translation: {
          welcome: 'Willkommen zu Deiner Vue.js App',
          descr: 'Eine Anleitung und Rezepte zum Konfigurieren/Anpassen '
            + 'dieses Projekts findest du<br>in der '
            + '<a href="https://cli.vuejs.org" target="_blank" '
            + 'rel="noopener">vue-cli-Dokumentation</a>.'
        }
      }
    }
  });

export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

🥳 Отлично, вы только что создали свой первый переключатель языков!

Благодаря i18next-browser-languagedetector теперь он пытается определить язык браузера и автоматически использовать этот язык, если вы предоставили для него переводы. Язык, выбранный вручную в переключателе языков, сохраняется в локальном хранилище, и при следующем посещении страницы этот язык будет использоваться в качестве предпочтительного.

Как получить текущий язык?

Начиная с i18next v21 существует i18next.resolvedLanguage.
Он установлен на текущий разрешенный язык и может использоваться в качестве основного используемого языка, например, в переключателе языков.

Если ваш обнаруженный язык, например, en-US, и вы предоставили переводы только для en (fallbackLng) вместо i18next.resolvedLanguage будет возвращено en.

i18next.language, i18next.languages ​​и i18next.resolvedLanguage

/* language */
i18next.language;
// Is set to the current detected or set language.
/* language */
i18next.languages;
// Is set to an array of language codes that will be used to look up the translation value.
// When the language is set, this array is populated with the new language codes.
// Unless overridden, this array is populated with less-specific versions of that code for fallback purposes, followed by the list of fallback languages
// initialize with fallback languages
i18next.init({
  fallbackLng: ["es", "fr", "en-US", "dev"]
});
// change the language
i18next.changeLanguage("en-US-xx");
// new language and its more generic forms, followed by fallbacks
i18next.languages; // ["en-US-xx", "en-US", "en", "es", "fr", "dev"]
// change the language again
i18next.changeLanguage("de-DE");
// previous language is not retained
i18next.languages; // ["de-DE", "de", "es", "fr", "en-US", "dev"]
/* resolvedLanguage */
i18next.resolvedLanguage;
// Is set to the current resolved language.
// It can be used as primary used language,
// for example in a language switcher.

Интерполяция и плюрализация

i18next не ограничивается только стандартными функциями i18n.
Но наверняка он способен обрабатывать множественное число и интерполяцию.

Если вам интересно посмотреть, как это работает, взгляните на этот раздел в той другой записи в блоге.

Форматирование

Также возможно форматирование.

Если вам интересно посмотреть, как это работает, взгляните на этот раздел в той другой записи в блоге.

Контекст

Как насчет конкретного приветственного сообщения, основанного на текущем дневном времени? то есть утро, вечер и т. д.
Это возможно благодаря контекстной функции i18next.

Если вам интересно посмотреть, как это работает, взгляните на этот раздел в той другой записи в блоге.

Отделить переводы от кода

Наличие переводов в нашем файле i18n.js работает, но не совсем подходит для работы с переводчиками.
Давайте отделим переводы от кода и поместим их в специальные файлы json.

Поскольку это веб-приложение, i18next-http-backend поможет нам в этом.

npm install i18next-http-backend

Переместите переводы в общую папку:

Адаптируйте файл i18n.js для использования i18next-http-backend:

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'
import Backend from 'i18next-http-backend'
i18next
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en'
  });
export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

Теперь переводы загружаются асинхронно.
Если у вас медленное подключение к сети, вы можете заметить, что до загрузки переводов отображаются только ключи i18n.

Чтобы предотвратить это, мы используем новую функциональность Приостановка Vue.js.

Сначала давайте адаптируем файл i18n.js, экспортировав обещание инициализации i18next:

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'
import Backend from 'i18next-http-backend'
export const
  i18nextPromise = i18next
  // i18next-http-backend
  // loads translations from your server
  // https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en'
  });
export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

…и используйте это обещание в App.vue:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <TranslationShowCase />
</template>
<script>
import TranslationShowCase from './components/TranslationShowCase.vue'
import { i18nextPromise } from './i18n.js'
export default {
  name: 'App',
  components: {
    TranslationShowCase
  },
  // used in combination with Suspense.
  // useful when translations are not in-memory...
  async setup() {
    await i18nextPromise
    return {}
  }
}
</script>

Давайте создадим новый файл: например, Suspenser.vue:

<template>
  <Suspense>
    <template #default>
      <App />
    </template>
    <template #fallback>
      <div>
        <img alt="Vue logo" src="./assets/logo.png">
        <h1>Loading...</h1>
      </div>
    </template>
  </Suspense>
</template>
<script>
import App from './App.vue'
export default {
  name: 'Suspenser',
  components: {
    App
  }
}
</script>

И используйте это в своем файле main.js:

import { createApp } from 'vue'
import i18n from './i18n'
import App from './Suspenser.vue'
i18n(createApp(App)).mount('#app')

Теперь, пока ваши переводы загружены, вы увидите резервный шаблон:

Теперь ваше приложение выглядит так же, но ваши переводы разделены.
Если вы хотите поддерживать новый язык, вы просто создаете новую папку и новый JSON-файл перевода.
Это дает вам возможность отправить переводы некоторым переводчикам.
Или, если вы работаете с системой управления переводами, вы можете просто синхронизировать файлы с cli.

Лучшее управление переводами

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

Есть ли вариант получше?

Конечно!

i18next помогает перевести приложение, и это здорово, но это еще не все.

  • Как вы интегрируете какие-либо переводческие услуги/агентства?
  • Как вы отслеживаете новый или удаленный контент?
  • Как вы справляетесь с правильным управлением версиями?
  • Как вы развертываете изменения перевода без развертывания всего приложения?
  • и многое другое…

Ищу что-то подобное❓

Как это выглядит?

Сначала нужно зарегистрироваться в locize и войти.
Затем создать новый проект в locize и добавить свои переводы. Вы можете добавить свои переводы либо с помощью cli, либо импортируя отдельные файлы json, либо через API.

Сделав это, мы заменим i18next-http-backend на i18next-locize-backend.

npm install i18next-locize-backend

После импорта переводов для локализации удалите папку locales.

Адаптируйте файл i18n.js для использования i18next-locize-backend и убедитесь, что вы скопировали идентификатор проекта и ключ API из вашего проекта locize:

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
const locizeOptions = {
  projectId: '94c21299-0cf5-4ad3-92eb-91f36fc3f20f',
  apiKey: 'bc8586d9-fceb-489c-86ac-2985393ed955', // YOU should not expose your apps API key to production!!!
  version: 'latest'
}
export const
  i18nextPromise = i18next
                    // i18next-locize-backend
                    // loads translations from your project, saves new keys to it (saveMissing: true)
                    // https://github.com/locize/i18next-locize-backend
                    .use(Backend)
                    // detect user language
                    // learn more: https://github.com/i18next/i18next-browser-languageDetector
                    .use(LanguageDetector)
                    // init i18next
                    // for all options read: https://www.i18next.com/overview/configuration-options
                    .init({
                      debug: true,
                      fallbackLng: 'en',
                      backend: locizeOptions
                    })
export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

i18next-locize-backend предлагает функциональность для получения доступных языков непосредственно из locize, давайте воспользуемся ею:

<template>
  <div class="hello">
    <h1>{{ $t('welcome') }}</h1>
    <p v-html="$t('descr')"></p>
    <i>{{ $t('new.key', 'this will be added automatically') }}</i>
    <hr />
    <div>
      <div v-if="languages">
        <span v-for="(lng, index) in Object.keys(languages)" :key="lng">
          <a v-if="$i18next.resolvedLanguage !== lng" v-on:click="$i18next.changeLanguage(lng)">
            {{ languages[lng].nativeName }}
          </a>
          <strong v-if="$i18next.resolvedLanguage === lng">
            {{ languages[lng].nativeName }}
          </strong>
          <span v-if="index < (Object.keys(languages).length - 1)">&nbsp;|&nbsp;</span>
        </span>
      </div>
    </div>
  </div>
</template>
<script>
import i18next from 'i18next'
export default {
  name: 'TranslationShowCase',
  data () {
    return {
      languages: []
    }
  },
  async mounted () {
    this.languages = await i18next.services.backendConnector.backend.getLanguages()
  }
}
</script>

сохранить недостающие переводы

Благодаря использованию функции saveMissing новые ключи добавляются для автоматического определения местоположения при разработке приложения.

Просто передайте saveMissing: true в параметрах i18next:

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
const locizeOptions = {
  projectId: '94c21299-0cf5-4ad3-92eb-91f36fc3f20f',
  apiKey: 'bc8586d9-fceb-489c-86ac-2985393ed955', // YOU should not expose your apps API key to production!!!
  version: 'latest'
}
export const
  i18nextPromise = i18next
                    // i18next-locize-backend
                    // loads translations from your project, saves new keys to it (saveMissing: true)
                    // https://github.com/locize/i18next-locize-backend
                    .use(Backend)
                    // detect user language
                    // learn more: https://github.com/i18next/i18next-browser-languageDetector
                    .use(LanguageDetector)
                    // init i18next
                    // for all options read: https://www.i18next.com/overview/configuration-options
                    .init({
                      debug: true,
                      fallbackLng: 'en',
                      backend: locizeOptions,
                      saveMissing: true
                    })
export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

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

<i>{{ $t('new.key', 'this will be added automatically') }}</i>

приведет к локализации следующим образом:

👀 но есть еще…

Благодаря плагину locize-lastused вы сможете находить и фильтровать в locize, какие ключи используются или уже не используются.

С помощью плагина locize вы сможете использовать свое приложение в locize InContext Editor.

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

Посмотрите это видео, чтобы увидеть, как выглядит рабочий процесс автоматического машинного перевода!

npm install locize-lastused locize

используйте их в i18n.js:

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import LastUsed from 'locize-lastused'
import { locizePlugin } from 'locize'
const locizeOptions = {
  projectId: '94c21299-0cf5-4ad3-92eb-91f36fc3f20f',
  apiKey: 'bc8586d9-fceb-489c-86ac-2985393ed955', // YOU should not expose your apps API key to production!!!
  version: 'latest'
}
export const
  i18nextPromise = i18next
                    // locize-lastused
                    // sets a timestamp of last access on every translation segment on locize
                    // -> safely remove the ones not being touched for weeks/months
                    // https://github.com/locize/locize-lastused
                    .use(LastUsed)
                    // locize-editor
                    // InContext Editor of locize
                    .use(locizePlugin)
                    // i18next-locize-backend
                    // loads translations from your project, saves new keys to it (saveMissing: true)
                    // https://github.com/locize/i18next-locize-backend
                    .use(Backend)
                    // detect user language
                    // learn more: https://github.com/i18next/i18next-browser-languageDetector
                    .use(LanguageDetector)
                    // init i18next
                    // for all options read: https://www.i18next.com/overview/configuration-options
                    .init({
                      debug: true,
                      fallbackLng: 'en',
                      saveMissing: true,
                      backend: locizeOptions,
                      locizeLastUsed: locizeOptions
                    })
export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

Автоматический машинный перевод:

Фильтр последних использованных переводов:

Контекстный редактор:

📦 Подготовимся к производству 🚀

Теперь мы готовим приложение к выходу в производство.

Сначала в locize создайте специальную версию для производства. Не включайте автоматическую публикацию для этой версии, а публикуйте вручную или через API или через CLI.
Наконец, включите максимальный возраст Cache-Control для этой рабочей версии.

Давайте воспользуемся особенностью окружения реактивных скриптов.

Давайте создадим файл среды по умолчанию, один для разработки и один для производства:

.env:

VUE_APP_LOCIZE_PROJECTID=94c21299-0cf5-4ad3-92eb-91f36fc3f20f

.env.development:

VUE_APP_LOCIZE_VERSION=latest
VUE_APP_LOCIZE_APIKEY=bc8586d9-fceb-489c-86ac-2985393ed955

.env.production:

VUE_APP_LOCIZE_VERSION=production

Теперь адаптируем файл i18n.js:

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import LastUsed from 'locize-lastused'
import { locizePlugin } from 'locize'
const isProduction = process.env.NODE_ENV === 'production'
const locizeOptions = {
  projectId: process.env.VUE_APP_LOCIZE_PROJECTID,
  apiKey: process.env.VUE_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
  version: process.env.VUE_APP_LOCIZE_VERSION
}
if (!isProduction) {
  // locize-lastused
  // sets a timestamp of last access on every translation segment on locize
  // -> safely remove the ones not being touched for weeks/months
  // https://github.com/locize/locize-lastused
  i18next.use(LastUsed);
}
export const
  i18nextPromise = i18next
                    // locize-editor
                    // InContext Editor of locize
                    .use(locizePlugin)
                    // i18next-locize-backend
                    // loads translations from your project, saves new keys to it (saveMissing: true)
                    // https://github.com/locize/i18next-locize-backend
                    .use(Backend)
                    // detect user language
                    // learn more: https://github.com/i18next/i18next-browser-languageDetector
                    .use(LanguageDetector)
                    // init i18next
                    // for all options read: https://www.i18next.com/overview/configuration-options
                    .init({
                      debug: !isProduction,
                      fallbackLng: 'en',
                      saveMissing: !isProduction,
                      backend: locizeOptions,
                      locizeLastUsed: locizeOptions
                    })
export default function (app) {
  app.use(I18NextVue, { i18next })
  return app
}

Теперь, во время разработки, вы продолжите сохранять отсутствующие ключи и использовать функцию lastused. =› запуск npm подача

А в продакшене saveMissing и lastused отключены, а API-ключ не выставлен. => npm запустить сборку

Кэширование:

Объединение версий:

🧑‍💻 Полный код можно найти здесь.

Посмотрите также часть интеграции кода в этом видео YouTube.

🎉🥳 Поздравляем 🎊🎁

Надеюсь, вы узнали что-то новое о i18next, локализации Vue.js и современных рабочих процессах локализации.

Так что если вы хотите вывести свою тему i18n на новый уровень, стоит попробовать платформу управления локализацией — locize.

Основатели locize также являются создателями i18next. Таким образом, используя locize, вы напрямую поддерживаете будущее i18next.

👍