Подключение через CDN

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

Библиотека загружается напрямую в браузер через тег <script>, после чего становится доступной глобально через объект i18next.


Что такое CDN

CDN (Content Delivery Network) — сеть серверов для распространения статических файлов. Вместо локальной установки библиотека загружается из внешнего источника.

Подключение через CDN позволяет:

  • быстро начать работу;
  • не использовать npm;
  • избежать настройки bundler;
  • тестировать библиотеку в обычном HTML-файле.

Наиболее популярные CDN:

  • jsDelivr
  • unpkg
  • cdnjs

Базовое подключение библиотеки

Минимальный пример подключения I18next:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>I18next CDN</title>
</head>
<body>

<h1 id="title"></h1>

<script src="https://unpkg.com/i18next@23.11.5/dist/umd/i18next.min.js"></script>

<script>
    i18next.init({
        lng: 'ru',
        resources: {
            ru: {
                translation: {
                    title: 'Главная страница'
                }
            }
        }
    }, function(err, t) {
        document.getElementById('title').textContent = t('title');
    });
</script>

</body>
</html>

Как работает подключение

После загрузки файла:

<script src="https://unpkg.com/i18next/dist/umd/i18next.min.js"></script>

в глобальной области появляется объект:

i18next

Через него выполняются:

  • инициализация;
  • переключение языков;
  • получение переводов;
  • загрузка ресурсов;
  • настройка параметров локализации.

Метод init

Главный метод настройки библиотеки:

i18next.init(options, callback)

Параметры

Параметр Назначение
lng текущий язык
resources словари переводов
fallbackLng резервный язык
debug режим отладки
ns namespaces
defaultNS namespace по умолчанию

Структура resources

Все переводы обычно описываются внутри объекта resources.

Пример:

resources: {
    en: {
        translation: {
            welcome: 'Welcome',
            button: 'Save'
        }
    },
    ru: {
        translation: {
            welcome: 'Добро пожаловать',
            button: 'Сохранить'
        }
    }
}

Структура имеет несколько уровней:

resources
 └── язык
      └── namespace
            └── ключи перевода

Namespace translation

По умолчанию используется namespace:

translation

Поэтому запись:

t('welcome')

эквивалентна:

t('translation:welcome')

Получение перевода

Для получения текста используется функция:

i18next.t()

Пример:

const text = i18next.t('welcome');
console.log(text);

Пример нескольких языков

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Languages</title>
</head>
<body>

<h1 id="title"></h1>

<script src="https://unpkg.com/i18next@23.11.5/dist/umd/i18next.min.js"></script>

<script>
    i18next.init({
        lng: 'en',
        fallbackLng: 'en',

        resources: {
            en: {
                translation: {
                    title: 'Home page'
                }
            },

            ru: {
                translation: {
                    title: 'Главная страница'
                }
            }
        }
    }, function () {
        document.getElementById('title').textContent =
            i18next.t('title');
    });
</script>

</body>
</html>

Смена языка

Язык можно изменить динамически.

Метод changeLanguage

i18next.changeLanguage('ru');

Полный пример:

<button oncl ick="setRu()">RU</button>
<button oncl ick="setEn()">EN</button>

<h1 id="title"></h1>

<script>
    function render() {
        document.getElementById('title').textContent =
            i18next.t('title');
    }

    i18next.init({
        lng: 'en',

        resources: {
            en: {
                translation: {
                    title: 'Settings'
                }
            },

            ru: {
                translation: {
                    title: 'Настройки'
                }
            }
        }
    }, render);

    function setRu() {
        i18next.changeLanguage('ru', render);
    }

    function setEn() {
        i18next.changeLanguage('en', render);
    }
</script>

Асинхронная природа init

Метод init() работает асинхронно.

Неправильно:

i18next.init({...});

console.log(i18next.t('title'));

Возможна ситуация, когда инициализация ещё не завершилась.

Правильно:

i18next.init({...}, function () {
    console.log(i18next.t('title'));
});

Либо через Promise:

i18next.init({...}).then(() => {
    console.log(i18next.t('title'));
});

Подключение с defer

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

<script
    defer
    src="https://unpkg.com/i18next/dist/umd/i18next.min.js">
</script>

Преимущества:

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

Использование debug

Режим отладки помогает видеть работу библиотеки.

i18next.init({
    debug: true
});

В консоли браузера будут отображаться:

  • загруженные ресурсы;
  • текущий язык;
  • ошибки переводов;
  • fallback;
  • namespaces.

Fallback language

Резервный язык используется при отсутствии перевода.

i18next.init({
    lng: 'de',

    fallbackLng: 'en',

    resources: {
        en: {
            translation: {
                hello: 'Hello'
            }
        }
    }
});

Если немецкий перевод отсутствует:

i18next.t('hello')

вернёт:

Hello

Отсутствующие ключи

Если перевод не найден:

i18next.t('unknown')

по умолчанию возвращается сам ключ:

unknown

Это удобно при разработке, поскольку сразу видно отсутствующие переводы.


Интерполяция

I18next поддерживает вставку переменных в строки.

Переводы

resources: {
    ru: {
        translation: {
            welcome: 'Привет, {{name}}'
        }
    }
}

Использование

i18next.t('welcome', {
    name: 'Алексей'
});

Результат:

Привет, Алексей

HTML-экранирование

По умолчанию I18next экранирует HTML.

interpolation: {
    escapeValue: true
}

Это защищает от XSS-атак.

Для браузерных приложений иногда используется:

interpolation: {
    escapeValue: false
}

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


Вложенные ключи

Переводы можно группировать.

translation: {
    menu: {
        home: 'Главная',
        profile: 'Профиль'
    }
}

Получение значения:

i18next.t('menu.home');

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

translation: {
    errors: [
        'Ошибка сети',
        'Ошибка сервера'
    ]
}

Получение:

i18next.t('errors.0');

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

Обычно переводы выносятся в отдельные файлы.

ru.json

{
    "title": "Главная",
    "button": "Сохранить"
}

en.json

{
    "title": "Home",
    "button": "Save"
}

Подключение HTTP Backend через CDN

Для загрузки переводов из файлов используется дополнительный плагин:

<script src="https://unpkg.com/i18next-http-backend@2.5.2/i18nextHttpBackend.min.js"></script>

После этого можно загружать JSON автоматически.


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

<script src="https://unpkg.com/i18next@23.11.5/dist/umd/i18next.min.js"></script>

<script src="https://unpkg.com/i18next-http-backend@2.5.2/i18nextHttpBackend.min.js"></script>

<script>
    i18next
        .use(i18nextHttpBackend)
        .init({

            lng: 'ru',

            backend: {
                loadPath: '/locales/{{lng}}.json'
            }

        }, function () {

            console.log(i18next.t('title'));

        });
</script>

Структура каталогов

Пример организации проекта:

project/
│
├── index.html
│
├── locales/
│   ├── ru.json
│   └── en.json
│
└── js/
    └── app.js

Пример ru.json

{
    "title": "Главная страница",
    "login": "Войти",
    "logout": "Выйти"
}

Пример en.json

{
    "title": "Home page",
    "login": "Login",
    "logout": "Logout"
}

Автоматическое определение языка

Для определения языка браузера существует плагин:

<script src="https://unpkg.com/i18next-browser-languagedetector@7.2.0/i18nextBrowserLanguageDetector.min.js"></script>

Использование:

i18next
    .use(i18nextBrowserLanguageDetector)
    .init({
        fallbackLng: 'en'
    });

Библиотека может определять язык:

  • из браузера;
  • из URL;
  • из cookie;
  • из localStorage;
  • из query string.

Последовательность поиска языка

Настраивается параметром:

detection: {
    order: [
        'querystring',
        'cookie',
        'localStorage',
        'navigator'
    ]
}

Кэширование языка

detection: {
    caches: ['localStorage']
}

После выбора языка значение сохраняется в браузере.


Подключение нескольких плагинов

i18next
    .use(i18nextHttpBackend)
    .use(i18nextBrowserLanguageDetector)
    .init({
        fallbackLng: 'en'
    });

Использование CDNJS

Альтернативное подключение:

<script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/23.11.5/i18next.min.js"></script>

Использование jsDelivr

<script src="https://cdn.jsdelivr.net/npm/i18next@23.11.5/dist/umd/i18next.min.js"></script>

Проверка загрузки библиотеки

После подключения можно проверить:

console.log(i18next);

Если библиотека загружена успешно, в консоли появится объект API.


Типичные ошибки подключения

Неверный порядок скриптов

Ошибка:

<script>
    i18next.init();
</script>

<script src="i18next.min.js"></script>

Правильно:

<script src="i18next.min.js"></script>

<script>
    i18next.init();
</script>

Ошибка CORS

При загрузке JSON-файлов через file:// браузер может блокировать запросы.

Неправильно:

file:///project/index.html

Правильно запускать через локальный сервер:

http://localhost:3000

MIME type error

Если сервер неправильно отдаёт JSON:

application/json

могут возникать ошибки загрузки переводов.


Ошибка отсутствия namespace

Если namespace не найден:

i18next.t('common:title')

но namespace common не подключён, перевод не будет найден.


Подключение namespace

i18next.init({

    ns: ['common', 'auth'],

    defaultNS: 'common'

});

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

resources: {
    ru: {

        common: {
            save: 'Сохранить'
        },

        auth: {
            login: 'Вход'
        }
    }
}

Использование:

i18next.t('auth:login');

Динамическое обновление DOM

Обычно после смены языка требуется повторный рендер интерфейса.

Пример:

function updateContent() {

    document.querySelector('#title').innerHTML =
        i18next.t('title');

    document.querySelector('#login').innerHTML =
        i18next.t('login');
}

Использование data-атрибутов

Удобный способ локализации HTML.

HTML

<h1 data-i18n="title"></h1>

<button data-i18n="save"></button>

Javascript

function translatePage() {

    document.querySelectorAll('[data-i18n]')
        .forEach(element => {

            const key = element.dataset.i18n;

            element.textContent = i18next.t(key);

        });
}

Полный пример приложения

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">

    <title>I18next App</title>

    <script
        src="https://unpkg.com/i18next@23.11.5/dist/umd/i18next.min.js">
    </script>

    <script
        src="https://unpkg.com/i18next-http-backend@2.5.2/i18nextHttpBackend.min.js">
    </script>

    <script
        src="https://unpkg.com/i18next-browser-languagedetector@7.2.0/i18nextBrowserLanguageDetector.min.js">
    </script>
</head>
<body>

<button id="ru">RU</button>
<button id="en">EN</button>

<h1 data-i18n="title"></h1>

<p data-i18n="description"></p>

<script>

i18next
    .use(i18nextHttpBackend)
    .use(i18nextBrowserLanguageDetector)
    .init({

        fallbackLng: 'en',

        debug: true,

        backend: {
            loadPath: '/locales/{{lng}}.json'
        }

    }, updateContent);

function updateContent() {

    document.querySelectorAll('[data-i18n]')
        .forEach(element => {

            element.textContent =
                i18next.t(element.dataset.i18n);

        });
}

document
    .getElementById('ru')
    .addEventListener('click', () => {

        i18next.changeLanguage('ru', updateContent);

    });

document
    .getElementById('en')
    .addEventListener('click', () => {

        i18next.changeLanguage('en', updateContent);

    });

</script>

</body>
</html>