Статьи

Пошаговое руководство по локализации JavaScript

Интернационализация (как окрестили I18n) и локализации (дублированные l10n) являются очень важными (хотя часто жесткими ) шагами для любого приложения, которое будет использоваться по всему миру. В одной из предыдущих статей мы увидели, как реализовать I18n на серверной части на основе Ruby on Rails, но сегодня пришло время поговорить о клиентской части. В этой статье мы обсудим, как локализовать приложения JavaScript, используя следующие решения:

  • jQuery.I18n от Викимедиа
  • Полиглот от Airbnb
  • Глобализация командой jQuery

Все эти решения совершенно разные и имеют свои особенности, поэтому мы увидим их в действии.

Исходный код доступен на GitHub .

Препараты

Прежде чем перейти к основной части, давайте быстро подготовим базовую структуру для нашего простого демонстрационного проекта. Создайте отдельную папку с файлом index.html внутри. Мы сделаем копии этого файла, чтобы протестировать различные решения. Затем создайте вложенную папку с именем common и поместите внутрь файл jquery.js, который можно загрузить с веб-сайта jquery.com . Вы можете выбрать любую версию jQuery (1, 2 или 3) в зависимости от того, какие браузеры вы хотите поддерживать — для этой демонстрации это не имеет значения.

Теперь заполните index.html разметкой:

index.html

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
        <script src="common/jquery.js"></script>
    </head>
    <body>

    </body>
    </html>

Этого достаточно для нас, чтобы начать!

jQuery.I18n

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

Переводы для jQuery.I18n могут быть разделены на несколько файлов ( en.json , de.json и т. Д.) Или сохранены вместе в одном файле. Вот пример en.json :

{
    "@metadata": {
        "authors": [
          "Me"
        ],
        "last-updated": "2016-09-21",
        "locale": "en",
        "message-documentation": "qqq"
    },
    "appname-title": "Example Application",
    "appname-sub-title": "An example application with jquery.i18n"
}

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

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

{
  "@metadata": { ... }
    "en": {
    "appname-title": "Example Application"
    },
    "ru": {
        "appname-title": "Тестовое приложение"
    }
}

Более того, вы даже можете указать путь к файлу переводов:

{
  "@metadata": { ... }
    "en": {
    "appname-title": "Example Application"
    },
    "ru": "ru.yml"
}

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

$.i18n().load({
  'en': {
    'appname-title': 'Example Application'
  },
  'ru' : {
    'appname-title': 'Тестовое приложение'
  }
});

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

Другим решением является загрузка сообщений с внешнего URL

$.i18n().load({
    en: 'i18n/en.json'
})

При использовании этого подхода функция loadвернет обещание, поэтому вы можете поместить дальнейшие действия внутри doneфункции:

$.i18n().load({
    en: 'i18n/en.json'
}).done( function() { console.log('done!') } )

Вы можете установить локаль при инициализации библиотеки, используя любую из localeопций

$.i18n( {
locale: 'en'
} );

или предоставив его внутри langатрибута для htmlтега:

<html lang="en" dir="ltr">

Конечно, локаль может быть переключена позже, переопределив localeопцию:

$.i18n({
  locale: 'de' 
});
// or
$.i18n().locale = 'de';

Чтобы перевести сообщение, вы должны предоставить ключ к $.i18nфункции

$.i18n('message-key1');

или просто использовать в data-атрибут (без дополнительных JavaScript не требуется). Исходное содержимое является резервным текстом для отображения в случае, если что-то идет не так и перевод не может быть найден:

<li data-i18n="message-key">Fallback text</li>

Обратите внимание, что сообщения поддерживают интерполяцию, принимая параметры. Они написаны как $1, и $2т.д .:

var message = "Good day, $1";
$.i18n(message, 'Joe');

Множественные и пол обрабатываются с помощью следующего синтаксиса:

var message = "Found $1 {{PLURAL:$1|result|results}}";
$.i18n(message, 1);

var message = "$1 changed {{GENDER:$2|his|her}} profile picture";
$.i18n(message, 'Emma', 'female');

На практике

Для начала скопируйте наш базовый файл index.html как jquery_i18n.html . Создайте новый каталог jquery_i18n и поместите в него файл main-jquery_i18n.js . Следующий клон jQuery.I18n где-нибудь на вашем компьютере вместе с субмодулями:

    $ git clone https://github.com/wikimedia/jquery.i18n.git
    $ cd jquery.i18n
    $ git submodule update --init

Нам потребуются все файлы из каталога src (без папки languages ), а также CLDRPluralRuleParser.js из libs \ CLDRPluralRuleParser \ src . Скопируйте все это в папку jquery_i18n и затем включите их в правильном порядке:

jquery_i18n.html

    [...]
  <script src="jquery_i18n/CLDRPluralRuleParser.js"></script>
  <script src="jquery_i18n/jquery.i18n.js"></script>
  <script src="jquery_i18n/jquery.i18n.messagestore.js"></script>
  <script src="jquery_i18n/jquery.i18n.fallbacks.js"></script>
  <script src="jquery_i18n/jquery.i18n.language.js"></script>
  <script src="jquery_i18n/jquery.i18n.parser.js"></script>
  <script src="jquery_i18n/jquery.i18n.emitter.js"></script>
  <script src="jquery_i18n/jquery.i18n.emitter.bidi.js"></script>
  <script src="jquery_i18n/main-jquery_i18n.js"></script>
    [...]

Давайте также предоставим начальную локаль через langатрибут:

jquery_i18n.html

    [...]
    <html lang="en" dir="ltr">
    [...]

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

jquery_i18n.html

    [...]
    <body>
        <a href="#" class="lang-switch" data-locale="en">English</a> |
        <a href="#" class="lang-switch" data-locale="ru">Русский</a>
        <h1 data-i18n="welcome"></h1>
        <p id="messages"></p>
    </body>
    [...]

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

Основные-jquery_i18n.js

    [...]
    jQuery(document).ready(function() {
        $.i18n().load({
            'en': {
            'welcome': 'Welcome!',
            }
            'ru': {
            'welcome': 'Добро пожаловать!',
            }
        });
    });
    [...]

Обратите внимание на welcomeключ — то же имя используется в data-i18nатрибуте для h1тега. Таким образом, правильный перевод будет использоваться автоматически — все, что нам нужно сделать, это запустить этот процесс, вызвав i18n()функцию. Я извлеку это внутри update_texts:

Основные-jquery_i18n.js

    [...]
    jQuery(document).ready(function() {
      var update_texts = function() {
            $('body').i18n();
        };

        $.i18n().load({...});

        update_texts();
    });
    [...]

Теперь давайте позаботимся о переключении языков. Это просто — просто прослушайте clickсобытие, извлеките значение data-localeатрибута и установите соответствующий языковой стандарт:

Основные-jquery_i18n.js

    [...]
  $('.lang-switch').click(function(e) {
    e.preventDefault();
    $.i18n().locale = $(this).data('locale');
    update_texts();
  });
    [...]

Наконец, мы собираемся добавить перевод для этого #messagesраздела. Это будет немного сложнее:

Основные-jquery_i18n.js

    [...]
  $.i18n().load({
    'en': {
      'welcome': 'Welcome!',
      'message_from': '$1 has send you $2 {{plural:$2|message|messages}}. {{gender:$3|He|She}} is waiting for your response!'
    },
    'ru': {
      'welcome': 'Добро пожаловать!',
      'message_from': '$1 {{gender:$3|отправил|отправила}} вам $2 {{plural:$2|сообщение|сообщений|сообщения}}. {{gender:$3|Он|Она}} ждёт ответа!'
    }
  });
    [...]

Здесь плюрализм и гендерная информация используются одновременно. Для русского языка мне пришлось добавить больше опций, потому что правила плюрализации более сложные. Чтобы это работало, настройте update_texts()функцию:

Основные-jquery_i18n.js

    [...]
  var update_texts = function() {
    $('body').i18n();
    $('#messages').text($.i18n('message_from', 'Ann', 2, 'female'));
  };
    [...]

Теперь откройте страницу и попробуйте переключаться между языками — все должно работать просто великолепно!

Polyglot.js

Polyglot.js — это небольшое решение, созданное Airbnb (инжиниринговая компания), которое работает в браузере и в средах Common.js. Он поддерживает интерполяцию и множественное число, имея нулевые зависимости. Возьмите производственную версию здесь .

Чтобы начать работать с Polyglot, создайте его экземпляр :

var polyglot = new Polyglot();

Он основан на классе, поэтому вы можете работать с другим набором локалей одновременно.

Далее приведите список фраз :

polyglot.extend({
  "hello": "Hello"
});
// or
var polyglot = new Polyglot({phrases: {"hello": "Hello"}});

Как правило, документация предлагает подготовить хэш фраз на серверной части, а затем вывести их в scriptтег. Обратите внимание, что Polyglot не будет переводить — ваша работа — давать правильные фразы в зависимости от локали пользователя.

Фразы могут быть заменены или удалены полностью (например, чтобы освободить память) с помощью replaceили clearметодов соответственно .

Обратите внимание, что вложение также поддерживается:

polyglot.extend({
  "nav": {
    "sidebar": {
      "welcome": "Welcome"
    }
  }
});

Если вы пришли из мира Rails, интерполяция должна показаться вам знакомой:

polyglot.extend({
  "hello_name": "Hello, %{name}."
});

Чтобы выполнить фактический перевод, используйте tметод:

polyglot.t("hello_name", {name: "John"});

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

polyglot.t("nav.sidebar.welcome");

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

polyglot.locale("de")

или установите его при создании нового объекта, передав localeопцию.

Сообщения с множественным числом должны быть разделены четырьмя каналами ( ||||):

polyglot.extend({
  "num_cars": "%{smart_count} car |||| %{smart_count} cars",
});

Правильное сообщение будет выбрано на основе smart_countпараметра.

На практике

Теперь давайте быстро увидим это решение в действии. Скопируйте файл index.html и назовите его polyglot.html . Затем создайте папку polyglot и поместите в нее рабочую версию сценария. Также создайте файл main-polyglot.js и подключите все:

polyglot.html

    [...]
    <script src="polyglot/polyglot.js"></script>
    <script src="polyglot/main-polyglot.js"></script>
    [...]

Для этой демонстрации мы будем использовать шаблонизатор Underscore.js, который будет использоваться для визуализации контента (хотя вы можете придерживаться Handlebars или другого решения). Если вы раньше не работали с такими шаблонами, идея довольно проста: у вас есть разметка с некоторыми параметрами. Затем этот шаблон «компилируется» (имеется в виду, что параметры получают свои значения) и отображается на странице, как и любой другой HTML.

Поместите рабочую версию Underscore в общую папку и потребуйте ее:

polyglot.html

    [...]
  <script src="common/jquery.js"></script>
  <script src="common/underscore.js"></script>
    [...]

Я собираюсь разместить наш шаблон прямо на странице, в самом низу. Обратите внимание, что он должен быть обернут scriptтегом специального типа (чтобы браузеры не пытались обработать его как код JavaScript):

polyglot.html

    <script type="text/template" id="main-content">
      <p><%= hello %></p>
      <small><%= unread %></small>
    </script>

Теперь внутри скрипта давайте подождем, пока документ не будет готов, а затем создадим экземпляр класса Polyglot, предоставив два сообщения:

Основные-polyglot.js

    jQuery(document).ready(function() {
      var polyglot = new Polyglot({
        phrases: {
          "hello": "Hello, %{name}!",
          "unread": "You have %{smart_count} unread message |||| You have %{smart_count} unread messages"
        }
      });
    });

Здесь мы использовали интерполяцию и множественное число (обратите внимание на имя параметра smart_count— при использовании другого имени множественное число перестает работать). Теперь давайте возьмем шаблон

    var main_content_temp = _.template($('#main-content').html());

а затем предоставить значения для параметров внутри него

  $('body').prepend(main_content_temp({
    hello: polyglot.t('hello', {name: 'John'}),
    unread: polyglot.t('unread', {smart_count: 2})
  }));

Вот результирующий код:

Основные-polyglot.js

    jQuery(document).ready(function() {
      var polyglot = new Polyglot({
        phrases: {
          "hello": "Hello, %{name}!",
          "unread": "You have %{smart_count} unread message |||| You have %{smart_count} unread messages"
        }
      });

      var main_content_temp = _.template($('#main-content').html());
      $('body').prepend(main_content_temp({
        hello: polyglot.t('hello', {name: 'John'}),
        unread: polyglot.t('unread', {smart_count: 2})
      }));
    });

Загрузите HTML-документ и протестируйте его!

Globalize

Глобализация — довольно большая библиотека для интернационализации, разработанная членами основной команды jQuery. Он работает в браузере ( поддерживает все современные браузеры и IE, начиная с версии 9) и с Node.js, предоставляя множество полезных функций, включая разбор числа, даты и времени, множественное число, интерполяцию, поддержку единиц и многое другое. В нем используются ключевые строительные блоки для программного обеспечения для поддержки языков мира, а также самый большой и обширный стандартный репозиторий доступных данных локали. Более того, Globalize является модульным и не содержит данных I18n — вы можете загрузить его самостоятельно.

Существует три основных функции API:

  • Globalize.load() загружает данные локали CLDR в формате JSON (например, форматы даты и времени, названия месяца и т. д.)
  • Globalize.locale() — геттер и сеттер для локали
  • [new] Globalize — создает новый объект Globalize

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

На практике

Давайте посмотрим Глобализация в действии прямо сейчас. Скопируйте index.html и назовите его globalize.html . Также создайте папку globalize с файлом globalize-main.js внутри. Пока Globalize является модульным, зависимости должны загружаться в правильном порядке (есть даже онлайн-инструмент, помогающий вам выяснить, какие зависимости необходимы).

Поэтому вам необходимо загрузить последнюю версию Globalize, а также CLDR . Вот список файлов Globalize для захвата (поместите их в папку globalize вашего проекта):

  • расстояние / globalize.js
  • расстояние / Globalize / date.js
  • расстояние / Globalize / number.js
  • расстояние / Globalize / currency.js
  • расстояние / Globalize / message.js
  • расстояние / Globalize / plural.js

К сожалению, это еще не все. Вот необходимые файлы CLDR, которые должны быть помещены в папку cldr (создайте ее сейчас):

  • DIST / cldr.js
  • расстояние / CLDR / event.js
  • расстояние / CLDR / supplemental.js

Уф. Последняя часть предоставляет некоторые общие данные о локали для CLDR — загрузите их здесь и поместите под именем cldr / cldr_data.js . Наконец, подключите все эти файлы в правильном порядке:

globalize.html

    [...]
  <script src="common/jquery.js"></script>

  <script src="cldr/cldr.js"></script>
  <script src="cldr/event.js"></script>
  <script src="cldr/supplemental.js"></script>

  <script src="globalize/globalize.js"></script>
  <script src="globalize/message.js"></script>
  <script src="globalize/number.js"></script>
  <script src="globalize/plural.js"></script>
  <script src="globalize/currency.js"></script>
  <script src="globalize/date.js"></script>

  <script src="cldr/cldr_data.js"></script>
  <script src="globalize/globalize-main.js"></script>
    [...]

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

globalize.html

    [...]
    <body>
        <h1 id="welcome"></h1>
        <p id="earnings"></p>
    </body>
    [...]

Теперь давайте загрузим приветственное сообщение:

глобализовать-main.js

    jQuery(document).ready(function() {
      Globalize.loadMessages({
        "en": {
          'welcome': 'Welcome, {name}!'
            }
        });
    });

Здесь мы используем модуль форматирования сообщений . Затем создайте экземпляр класса Globalize.

    var globalize = new Globalize("en");

и заполните #welcomeраздел:

    $('#welcome').text( globalize.messageFormatter('welcome')({name: 'John'}) );

Обратите внимание, что messageFormatterвозвращается функция, которую мы затем вызываем и передаем объект, содержащий имя. Это может быть переписано как

  var welcome_message = globalize.messageFormatter('welcome');
  $('#welcome').text( welcome_message({name: 'John'}) );

На самом деле, параметры сообщения не нужно называть — вы можете сказать 0, 1и 2т. Д .:

    'welcome': 'Welcome, {0}!'

В этом случае передайте массив в форматтер:

    $('#welcome').text(globalize.messageFormatter('welcome')(['John']));

Далее укажите другое сообщение, содержащее сегодняшний день и общий доход:

    "en": {
      'welcome': 'Welcome, {0}!',
      'earned': 'Today is {date} and you\'ve earned {amount}!'
    }

В этом примере мы будем использовать форматеры даты и валюты :

  $('#earnings').text(
      globalize.messageFormatter('earned')({
        amount: globalize.formatCurrency(500.5, 'USD'),
        date: globalize.formatDate( new Date(), {
          datetime: "medium"
        })
      })
  )

При форматировании валюты мы передаем USDвторой аргумент. Этот аргумент используется для отображения правильного символа при отображении результата. Сам символ был определен внутри файла clrd_data.js :

    "currencies": {
      "USD": {
        "symbol": "$"
      }
    }

mediumэто имя формата datetime — оно также было определено в файле clrd_data.js как

    "dateTimeFormats": {
      "medium": "{1}, {0}"
    }

Вот 1дата и 0время. Дата и время, в свою очередь, форматируются с использованием следующих масок:

    "dateFormats": {
      "medium": "MMM d, y"
    },
    "timeFormats": {
      "medium": "h:mm:ss a"
    }

Теперь, когда все готово, вы можете наблюдать за результатом!

PhraseApp снова спасает день

Управление переводами для нескольких языков действительно может быть утомительным. Однако с PhraseApp весь процесс становится намного проще.

PhraseApp поддерживает широкий спектр различных форматов от простого до вложенного JSON (и специальные форматы для AngularJS или React). Затем просто добавьте столько локалей, сколько вам нужно, и загрузите существующие файлы JSON с переводами, если они у вас есть.

Сделав это, вы сможете быстро понять, какие переводы отсутствуют, управлять своими ключами и сообщениями, а также загружать обновленные переводы одним щелчком мыши. Что еще круче, вы можете легко запросить поддержку у профессиональных переводчиков (что, вероятно, лучше, поскольку локализация касается не только перевода). Поэтому я настоятельно рекомендую вам попробовать PhraseApp!

Заключение

Итак, в этой статье мы рассмотрели различные решения, помогающие вам локализовать ваше приложение: jQuery.I18n, Globalize и Polyglot. Polyglot оказался самой маленькой и простой библиотекой, тогда как Globalize и jQuery.I18n довольно большие и сложные — выбор за вами!

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