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

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

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

Основные функции (часть 1)

  • Обнаружение и разрешение локали
  • Определение поддерживаемых локалей
  • Загрузка файла перевода для разрешенной локали
  • Ручная установка/принудительная настройка локали
  • Отображение переводов (извлечение из загруженного в данный момент файла перевода)

Интерполяция (часть 2)

  • Обработка динамических аргументов в наших строках перевода
  • Обработка форм единственного/множественного числа
  • Форматирование дат
  • Валюта форматирования

Этот набор функций является хорошим стартом для большинства приложений. В этой статье (часть 1) мы рассмотрим основные функции i18n. Мы займемся интерполяцией во второй части.

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

Машинопись

Мы будем использовать TypeScript для создания нашей библиотеки. Вы, наверное, уже слышали о TypeScript от Microsoft. Если нет, просто знайте, что это надмножество JavaScript со строгой типизацией. На самом деле он компилируется в JavaScript и помогает нам лучше организовывать и документировать наши программы. Код, написанный на TypeScript (или имеющий определения типов TypeScript), дает нам полезную информацию, такую ​​как сигнатуры функций и формы/интерфейсы объектов. Язык также помогает нам обнаруживать ошибки типов во время компиляции, поэтому мы экономим время, потому что обнаруживаем эти ошибки до запуска нашего кода. Здесь вы увидите TypeScript в действии, но не волнуйтесь, если вы этого не знаете. Вы можете в основном игнорировать ввод следующего кода и просто относиться к коду как к простому старому JavaScript. Для большинства намерений и целей это так.

Наш испытательный стенд

Наше тестовое приложение будет простой демонстрационной корзиной для покупок. Предположим, что это SPA (одностраничное приложение), которое мы хотим интернационализировать на клиенте. Чтобы сосредоточиться на работе браузера/JavaScript, мы будем жестко кодировать данные элементов и информацию о пользователях, которые обычно поступают с сервера. Вот код для нашего маленького испытательного стенда.

Примечание. Вы можете получить доступ ко всему коду приложения, созданного в этой статье, на Github.

src/components/App.tsx

import React, { Component } from 'react';

import './App.css';
import Cart from './Cart';
import SelectLanguage from './SelectLanguage';

class App extends Component {
    renderHeader() {
        return (
            <header className="App__Header">
                <h1>Your Cart</h1>

                <SelectLanguage
                    value="en"
                    onChange={() => { }}
                />
            </header>
        );
    }

    renderLead() {
        return (
Evening, Adam. Here’s what’s currently in your shopping cart.
Updated 21/10/2019
); } renderFooter() { return ( <p className=”App__Footer”> This is a demo to test the Gaia i18n library </p> ); } render() { return (
{this.renderHeader()} {this.renderLead()} {this.renderFooter()}
); } } export default App;

src/components/SelectLanguage.tsx

import React from 'react';

interface I18nConfig {
    supportedLocales: {
        [key: string]: string;
    };
}

const i18nConfig: I18nConfig = {
    supportedLocales: {
        en: 'English',
        ar: 'عربي',
        fr: 'Français',
    },
}

function renderOption(value: string, label: string) {
    return (<option key={value} value={value}>{label}</option>);
}

interface SelectLanuageProps {
    value: string;
    onChange(value: string): void;
}

function SelectLanguage(props: SelectLanuageProps) {
    return (
        <select
            value={props.value}
            onChange={e => props.onChange(e.target.value)}
        >
            {
                Object.keys(i18nConfig.supportedLocales).map((key) => {
                    return renderOption(key, i18nConfig.supportedLocales[key]);
                })
            }
        </select>
    );
}

export default SelectLanguage;

src/components/Cart.tsx

import React, { Component } from 'react';

import './Cart.css';

class Cart extends Component {
    render() {
        return (
            <table className="Cart">
                <thead>
                    <tr>
                        <th>Item</th>
                        <th>Quantity</th>
                        <th>Price</th>
                        <th>Subtotal</th>
                    </tr>
                </thead>

                <tbody>
                    <tr>
                        <td>Under-inflated balloons</td>
                        <td>2</td>
                        <td>$2.99</td>
                        <td>$5.98</td>
                    </tr>

                    <tr>
                        <td>Over-priced smartphone</td>
                        <td>1</td>
                        <td>$2299.99</td>
                        <td>$2299.99</td>
                    </tr>

                    <tr>
                        <td>Diamond-studded selfie stick</td>
                        <td>4</td>
                        <td>$284.99</td>
                        <td>$1139.96</td>
                    </tr>
                </tbody>
            </table>
        );
    }
}

export default Cart;

В основном это JSX, который определяет нашу разметку. Здесь у нас не так много возможностей для i18n, так как строки нашего пользовательского интерфейса жестко закодированы на английском языке. Возможно, вы заметили, что наш компонент SelectLanguage тоже мало что делает. Давайте исправим все это шаг за шагом, пока будем строить нашу библиотеку. Как советовал бы мудрый, начнем с самого начала.

Обнаружение локали

Нам нужен способ инициализировать нашу библиотеку, чтобы она определяла языковой стандарт пользователя из его или его настроек браузера. Давайте назовем нашу библиотеку Gaia в честь греческой богини-Матери-Земли. Может быть, наш API может работать следующим образом.

gaia.init()
    .then((locale) => {
        // finished loading and detected locale
    });

Давайте реализуем этот API.

src/gaia/gaia.ts

import { getUserLocale } from './lib/user-locale';

let _locale: string;

const gaia = {
    init(): Promise<string> {
        return new Promise((resolve, reject) => {
            _locale = getUserLocale();

            resolve(_locale);
        });
    }
};

export default gaia;

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

Примечание. Мы используем промис, потому что чуть позже проделаем кое-какую асинхронную работу, поэтому пока промис — это шаблонный шаблон.

Чтение локали пользователя из браузера

src/gaia/lib/user-locale.ts

declare global {
    interface Window {
        // IE navigator lanuage settings (non-standard)
        userLanguage: string;
        browserLanguage: string;
    }
}

export function getUserLocale(): string {
    return window.navigator.language ||
        window.browserLanguage ||
        window.userLanguage;
}

Наша функция получит языковой стандарт пользователя в соответствии с настройками его браузера, позаботившись о межбраузерных различиях за кулисами. Эта локаль, предоставляемая объектом window, будет первой, установленной в списке предпочтений браузера пользователя. Firefox на macOS, например, предоставляет следующий диалог для настройки локалей:ß

Первая локаль в этом списке предоставляется нам браузером и обрабатывается по-разному в разных браузерах. Стандартный способ доступа к локали — через window.navigator.language. Однако это свойство недоступно в Internet Explorer, поэтому, если мы хотим поддерживать этот браузер, нам нужно проверить одно из двух других свойств: window.browserLanguage и window.userLanguage. Наша функция сначала попробует стандартное свойство и при необходимости откажется от свойств IE.

Примечание. Чтобы использовать нестандартные свойства IE без возгласов TypeScript, мы используем ключевое слово declare, чтобы уведомить TypeScript о том, что они действительно являются свойствами window и их можно использовать.

Примечание.На момент написания стандартная функция window.navigator.language в Chrome для ПК, Android Chrome и браузере Android по умолчанию будет возвращать язык пользовательского интерфейса браузера, а не тот параметр, который пользователь установил в браузере.

Поддерживаемые локали и резервная локаль

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

Давайте переместим конфигурацию i18n нашего демонстрационного приложения из компонента SelectLanguage в отдельный файл для повторного использования. Мы также добавим к нему резервную настройку.

src/config/i18n.ts

interface I18nConfig {
    supportedLocales: {
        [key: string]: string;
    };

    fallbackLocale: string;
}

const i18nConfig: I18nConfig = {
    supportedLocales: {
        en: 'English',
        ar: 'عربي',
        fr: 'Français',
    },

    fallbackLocale: 'en',
};

export default i18nConfig;

Мы хотели бы иметь возможность передавать их в нашу библиотеку i18n и влиять на разрешение локали.

src/components/App.ts

// ...
import i18nConfig from '../config/i18n';

class App extends Component {
    componentDidMount() {
        const { supportedLocales, fallbackLocale } = i18nConfig;

        gaia
            .init({
                supportedLocales: Object.keys(supportedLocales),
                fallbackLocale
            })
            .then((locale) => {
                console.log({ locale });
            });
    }

// ...

Давайте обновим наш метод init, чтобы справиться с этим.

src/gaia/gaia.ts

import resolveUserLocale from './lib/user-locale';
import { normalize, containsNormalized } from './lib/util';

let _locale: string;
let _supportedLocales: ReadonlyArray<string>;

const gaia = {
    init(options: {
        supportedLocales: string[],
        fallbackLocale?: string,
    }): Promise<string> {
        return new Promise((resolve, reject) => {
            if (!options.supportedLocales ||
                options.supportedLocales.length === 0
            ) {
                return reject(new Error(
                    'No supported locales given. Please provide ' +
                    'supported locales.'
                ));
            }

            _supportedLocales = Object.freeze(
                options.supportedLocales.map(normalize)
            );

            if (options.fallbackLocale &&
                !gaia.isSupported(options.fallbackLocale)
            ) {
                return reject(new Error(
                    `Fallback locale ${options.fallbackLocale} is not in ` +
                    'supported locales given: ' +
                    `[${_supportedLocales.join(', ')}].`
                ));
            }

            _locale = resolveUserLocale(_supportedLocales) ||
                options.fallbackLocale ||
                _supportedLocales[0];

            resolve(_locale);
        });
    },

// ...

Сначала мы проверяем, был ли предоставлен требуемый теперь массив supportedLocales, сообщая пользователю, что у нас есть проблема, если это не так. Затем мы сохраняем список поддерживаемых локалей в частном порядке, чтобы его можно было кэшировать на потом. Мы normalize локалей перед их сохранением, что просто означает, что мы преобразуем все коды локалей в нижний регистр и заменяем символы подчеркивания тире. Таким образом, "en_US" нормализуется до "en-us". Это упростит сравнение локалей позже.

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

src/gaia/gaia.ts

// ...

const gaia = {
    
    // ...
    
    get supportedLocales(): ReadonlyArray<string> {
        return _supportedLocales;
    },

    isSupported(locale: string): boolean {
        return containsNormalized(gaia.supportedLocales, locale);
    },

    // ...

isSupported использует служебную функцию containsNormalized, чтобы убедиться, что данная локаль содержится в нашем поддерживаемом массиве локалей. containsNormalized просто сравнивает заданную локаль с нашими поддерживаемыми локалями без учета регистра или разделителя. Таким образом, если наш массив _supportedLocales содержит элемент "fr-fr", данные значения локали "fr-FR", "fr_FR" и "Fr_fR" будут считаться поддерживаемыми.

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

src/gaia/gaia.ts

const gaia = {
    init(
     // ...
    ) {
        // ...

            _locale = resolveUserLocale(_supportedLocales) ||
                options.fallbackLocale ||
                _supportedLocales[0];

// ...

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

src/gaia/lib/user-locale.ts

import { normalize, languageCode, find } from "./util";

export default function resolveUserLocale(
    supportedLocales: ReadonlyArray<string>,
): string | undefined {
    const userLocale = normalize(getUserLocale());

    const exactMatch = find(
        supportedLocales,
        (supported) => supported === userLocale,
    );

    if (exactMatch) { return exactMatch; }

    const userLanguageCode = languageCode(userLocale);

    const languageCodeMatch = find(
        supportedLocales,
        (supported) => languageCode(supported) === userLanguageCode,
    );

    if (languageCodeMatch) {
        return languageCodeMatch;
    }
}

Функция resolveUserLocale использует нашу ранее реализованную функцию getUserLocale для получения локали браузера пользователя. Затем он сравнивает эту локаль с заданным массивом поддерживаемых локалей. Если он находит точное совпадение, он разрешает это. В противном случае он проверяет, имеет ли языковой стандарт пользователя тот же язык, что и один из поддерживаемых языковых стандартов, и разрешает первое совпадение языка, если оно его находит.

Таким образом, если наш список поддерживаемых локалей — ["en-us", "ar-eg", "fr"], а локаль браузера пользователя — "en-IR", то разрешенная локаль будет "en-us". Точно так же "ar-SA", "en" и "fr-CA" будут соответствовать соответствующим поддерживаемым языковым настройкам. Конечно, если локаль пользователя точно совпадает с "ar-eg", мы примем это решение.

ПримечаниеresolveUserLocale использует пользовательскую функцию find. Эта функция просто использует Array.prototype.find, если он существует, и возвращается к циклической проверке for, если собственный Array.prototype.find не существует. Резервный вариант предназначен в первую очередь для работы с Internet Explorer, который не поддерживает собственный метод. Если вы хотите глубже погрузиться в код, ознакомьтесь с полным исходным кодом приложения на Github.

Примечание languageCode: при задании "en-us" возвращается "en".

Загрузка файлов перевода

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

public/lang/en.json

{
    "title": "Your Cart",
    "lead": "Evening, Adam. Here's what's currently in your shopping cart."
    
    // ...
}

Давайте создадим наш загрузчик и подключим его к инициализатору Gaia.

src/gaia/lib/load.ts

import 'whatwg-fetch';

export default function load(locale: string): Promise<Translations> {
    const url = `/lang/${locale}.json`;

    return fetch(url)
        .then((response) => {
            if (!response.ok) {
                throw new Error(
                    `${response.status}: Could not retrieve file at ${url}`
                );
            }

            return response.json();
        });
}

Наша функция load принимает локаль и получает JSON-файл ее перевода с помощью встроенного в браузер fetch API. Мы импортируем пакет whatwg-fetch, потому что он заполняет fetch для браузеров, которые его не поддерживают. После того, как мы захватим файл, мы просто разберем его в JSON, чтобы мы могли ввести в него ключ. Хорошо, давайте подключим наш загрузчик.

Примечание. Хотите знать, какой тип Translations TypeScript мы используем? Это просто интерфейс сопоставления строк: [key: string]: string.

src/gaia/gaia.ts

import load from './lib/load';

// ...

let _locale: string;
let _translations: Translations;
let _supportedLocales: ReadonlyArray<string>;

const gaia = {
    init(options: {
        supportedLocales: string[],
        fallbackLocale?: string,
    }): Promise<string> {
        return new Promise((resolve, reject) => {

            // ...

            _locale = resolveUserLocale(_supportedLocales) ||
                options.fallbackLocale ||
                _supportedLocales[0];

            return loadAndSet(_locale).then(() => resolve(_locale));
        });
    },

    // ...
};

export default gaia;

// Private
function loadAndSet(locale: string): Promise<void> {
    return new Promise((resolve, reject) => {
        if (!gaia.isSupported(locale)) {
            return reject(new Error(`Locale ${locale} is not in supported ` +
                `locales given: [${_supportedLocales.join(', ')}].`));
        }

        const normalizedLocale = normalize(locale);

        return load(normalizedLocale).then((json) => {
            _locale = normalizedLocale;
            _translations = json;

            return resolve();
        });
    });
}

Теперь мы фактически загружаем файл перевода в наш инициализатор. Для этого мы используем функцию loadAndSet, которая позволит нам повторно использовать соответствующую логику при ручной настройке локали (мы вернемся к этому через минуту). Поскольку в будущем loadAndSet будет использоваться вне инициализатора, он защищает от неподдерживаемых локалей. Затем он загружает файл перевода, используя нашу функцию load, и сохраняет JSON файла как текущий _translations для последующего извлечения. Функция также сохраняет заданную локаль в приватной переменной _locale, которая является источником достоверности нашей библиотеки для текущей локали.

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

Отображение перевода

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

h1Element.innerHTML = gaia.t('user_profile_title');

С нашей текущей реализацией функция t должна быть достаточно простой для построения.

/src/gaia/gaia.ts

// ...

let _translations: Translations;

// ...

const gaia = {
    
    // ...

    t(key: string): string {
        return _translations[key] || key;
    },
};

export default gaia;

export const t = gaia.t;

// ...

Мы просто берем индекс key и пытаемся получить доступ к соответствующему значению ключа из нашего загруженного _translations. Если ключ, к которому пытается получить доступ пользователь, не определен в текущем файле перевода, мы по умолчанию используем сам ключ, чтобы дать пользователю указание на отсутствие перевода. Это также позволяет ключам быть реальным содержимым на исходном языке, например на английском:

{
    "Hello my friend: "Bonjour, mon ami"
}

Поскольку gaia.t будет использоваться во всем нашем пользовательском интерфейсе, мы создаем для него именованный экспорт, t без пространства имен, удобное сокращение.

Хорошо, давайте обновим наш пользовательский интерфейс, чтобы использовать локализованные значения.

Интернационализация пользовательского интерфейса

src/components/App.tsx

import React, { Component } from 'react';

import './App.css';
import Cart from './Cart';
import gaia, { t } from '../gaia/gaia';
import i18nConfig from '../config/i18n';
import SelectLanguage from './SelectLanguage';

class App extends Component {
    state = {
        isLocaleDetermined: false,
    }

    componentDidMount() {
        const { supportedLocales, fallbackLocale } = i18nConfig;

        gaia
            .init({
                supportedLocales: Object.keys(supportedLocales),
                fallbackLocale
            })
            .then((locale) => {
                this.setState({ isLocaleDetermined: true });
            });
    }

    renderHeader() {
        return (
            <header className="App__Header">
                <h1>{t('title')}</h1>

                <SelectLanguage
                    value="en"
                    onChange={() => { }}
                />
            </header>
        );
    }

    renderLead() {
        return (
{t(‘lead’)}
{t(‘updated’)}
); } renderFooter() { return (
{t(‘footer’)}
); } renderContent() { return ( <> {this.renderHeader()} {this.renderLead()} {this.renderFooter()} </> ); } renderLoader() { return <p>Loading…</p>; } render() { return (
{this.state.isLocaleDetermined ? this.renderContent() : this.renderLoader() }
); } } export default App;

Мы показываем жестко запрограммированную загрузку текста, пока наша библиотека i18n инициализирует и загружает текущий файл перевода. Как только файл будет готов, мы покажем содержимое нашей страницы, которое теперь интернационализировано и будет содержать переведенный контент с помощью нашей новой функции t.

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

Ручная установка локали

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

src/gaia/gaia.ts

const gaia = {
    init(options: {
        supportedLocales: string[],
        locale?: string,
        fallbackLocale?: string,
    }): Promise<string> {
        return new Promise((resolve, reject) => {
            
            // ...

            if (options.locale) {
                _locale = options.locale;
            } else {
                _locale = resolveUserLocale(_supportedLocales) ||
                    options.fallbackLocale ||
                    _supportedLocales[0];
            }

            return loadAndSet(_locale).then(() => resolve(_locale));
        });
    },

// ...

Мы добавляем необязательный параметр locale в наш метод init. Если указан этот параметр, мы пропускаем автоматическое определение языкового стандарта и пытаемся установить его как принудительный языковой стандарт. Это позволяет разработчику указать локаль с самого начала, что удобно, если приложение сохранило локаль текущего пользователя из предыдущего посещения и хочет, например, перезагрузить ее.

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

src/gaia/gaia.ts

// ...

const gaia = {
    // ...

    get locale() { return _locale; },

    setLocale(locale: string): Promise<void> {
        return loadAndSet(locale);
    },

// ...

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

Теперь мы можем реализовать нашу функцию переключения языков.

src/components/App.tsx

// ...

class App extends Component {
    state = {
        locale: 'en',
        isLocaleDetermined: false,
    }

    componentDidMount() {
        const { supportedLocales, fallbackLocale } = i18nConfig;

        gaia
            .init({
                supportedLocales: Object.keys(supportedLocales),
                locale,
                fallbackLocale
            })
            .then((locale) => {
                this.setState({ locale, isLocaleDetermined: true });
            });
    }

    onSelectLocale = (newLocale: string) => {
        this.setState({ isLocaleDetermined: false }, () => {
            gaia.setLocale(newLocale)
                .then(() => this.setState({
                    locale: newLocale,
                    isLocaleDetermined: true
                }));
        });
    }

    renderHeader() {
        return (
            <header className="App__Header">
                <h1>{t('title')}</h1>

                <SelectLanguage
                    value={this.state.locale}
                    onChange={this.onSelectLocale}
                />
            </header>
        );
    }

// ...

Мы используем новый параметр locale для инициализации нашей библиотеки i18n с выбранной локалью, и мы подключаем наш селектор языка SelectLanguage так, чтобы gaia.setLocale вызывался, когда пользователь выбирает новую локаль. Это заставит Gaia загрузить языковой файл только что выбранной локали, и наш пользовательский интерфейс отреагирует на это. Теперь у нас есть селектор рабочего языка.

Засучив

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

У нас есть основы i18n, но нам нужно сделать еще немного. Нам нужно обрабатывать динамические аргументы в наших переведенных строках, множественном числе, датах и ​​валюте. Мы сделаем здесь паузу и вернемся к этим функциям интерполяции в следующей части этой серии, части 2. Надеюсь, вам понравилось это небольшое путешествие. Оставайтесь с нами для следующего :).

Примечание. Вы можете получить доступ ко всему приведенному выше коду приложения на GitHub.

Первоначально опубликовано в The Phrase Blog.