@uzum-tech/i18n
v1.1.1
Published
Uzum i18n
Downloads
231
Maintainers
Readme
О библиотеке
Uzum I18n — это облачная система интернационализации, которая позволяет управлять переводами через централизованную админку и автоматически синхронизировать их с вашими приложениями.
🎯 Основные концепции
- Централизованное управление: Все переводы хранятся в облаке и управляются через веб-интерфейс (Бэкенд реализован только для Uzum. Вам нужно реализовать его самостоятельно)
- Фреймворк-агностичность: Работает с любым JavaScript фреймворком через единый адаптер
- Реактивность: Автоматически обновляет интерфейс при смене языка или загрузке переводов
- Изоляция окружений: Отдельные переводы для development, staging и production
✨ Уникальные особенности
- Адаптивная интеграция — библиотека подстраивается под систему реактивности вашего фреймворка
- Облачная синхронизация — переводы загружаются из облака, не нужно хранить их в коде
- Горячая замена переводов — изменения в админке мгновенно отражаются в приложении
- Изоляция по окружениям — разные переводы для разных стендов через VPN
- Универсальный API — один код работает везде, меняется только адаптер реактивности
- Автоопределение языка по URL — если URL начинается с пути вида
/ru, язык при инициализации выбирается автоматически. Приоритетность: URL путь →localStorage→defaultLanguageCode→ язык по умолчанию в проекте → язык браузера - Сохранение выбора пользователя — если язык не указан в URL, а пользователь ранее уже открывал страницу и выбирал язык, будет восстановлен именно его последний выбор из
localStorage, а не язык по умолчанию
Содержание
Установка
Пакетный менеджер
Используя npm:
npm i @uzum-tech/i18nИспользуя yarn:
yarn add @uzum-tech/i18nИспользование
Vue
Примеры указаны для того, чтобы вы могли наглядно увидеть как можно использовать пакет и быстро разобраться в основных принципах работы. Любые решения могут зависеть от предпочтений принятых в вашем проекте.
- Заводим переменную окружения. Чтобы во всех стендах были одинаковые переводы, можно везде использовать
'production'.
# .env
VITE_I18N_ENV=production- Заводим переменную в файле конфига и прокидываем туда значение переменной окружения.
// @/shared/config/global-config.ts
export const globalConfig = {
i18n: {
env: import.meta.env.VITE_I18N_ENV
}
} as const;- Дополняем/Создаем файл локализации
i18n.ts. Создаем экземпляр локализацииLocalizationи 1ым аргументом передаем экземплярi18n, который реализует интерфейсI18n. Инстанс созданный методомcreateI18nиз пакетаvue-i18nпо умолчанию реализует его. 2ым аргументом передаем опции LocalizationOptions.
// @/shared/plugins/i18n.ts
// Если есть автоимпорты, то можно не импортировать ref
import { ref } from 'vue';
import { createI18n } from 'vue-i18n';
import { Localization } from '@uzum-tech/i18n';
import { globalConfig } from '../config/global-config';
const i18n = createI18n({
locale: 'ru',
legacy: false,
pluralRules: {
ru: YOUR_PLURAL_FUNCTION,
uz: YOUR_PLURAL_FUNCTION
}
});
export const localization = new Localization(i18n, {
projectKey: 'YOUR_PROJECT_KEY',
env: globalConfig.i18n.env,
ref
});
export default i18n;- Реализуем обертки для лоадинга и смены языка.
// @/app/components/app-layout-header/useAppLayoutHeader.ts
import { localization } from '@/shared/plugins/i18n';
export default function useAppLayoutHeader() {
const languagesLoading = computed<boolean>(
() => localization.languagesLoading.value || localization.messagesLoading.value
);
const selectLang = (value: string) => {
localization.changeLanguage(value);
};
return {
languagesLoading,
selectLang
};
}- Используем обертки для лоадинга и смены языка в шаблоне.
// @/app/components/app-layout-header/AppLayoutHeader.vue
<script lang="ts" scoped>
import useAppLayoutHeader from './useAppLayoutHeader';
const {
languagesLoading,
selectLang
} = useAppLayoutHeader();
</script>
<template>
...
<u-dropdown
...
@select="selectLang"
>
<u-button
...
:loading="languagesLoading"
>
...
</u-button>
</u-dropdown>
...
</template>React
- Создаем файл переменных окружения:
# .env
REACT_APP_I18N_ENV=production- Создаем файл конфигурации:
// src/shared/config/global-config.ts
export const globalConfig = {
i18n: {
env: process.env.REACT_APP_I18N_ENV
}
} as const;- Создаем файл локализации с React-совместимым i18n объектом:
// src/shared/plugins/i18n.ts
import { useState } from 'react';
import { Localization } from '@uzum-tech/i18n';
import { globalConfig } from '../config/global-config';
// Создаем React-совместимый i18n объект
const createReactI18n = () => {
const messages: Record<string, Record<string, string>> = {};
let currentLocale = 'ru';
let missingHandler: ((locale: string, key: string) => void) | null = null;
return {
global: {
locale: currentLocale,
setLocaleMessage: (language: string, msgs: Record<string, string>) => {
messages[language] = msgs;
},
mergeLocaleMessage: (language: string, msgs: Record<string, string>) => {
messages[language] = { ...messages[language], ...msgs };
},
setMissingHandler: (handler: (locale: string, key: string) => void) => {
missingHandler = handler;
}
}
};
};
// Создаем ref функцию для React
const createRef = <T>(value: T) => {
const [state, setState] = useState(value);
return {
get value() {
return state;
},
set value(newValue: T) {
setState(newValue);
}
};
};
const i18n = createReactI18n();
export const localization = new Localization(i18n, {
projectKey: 'YOUR_PROJECT_KEY',
env: globalConfig.i18n.env,
ref: createRef
});
export default i18n;- Создаем хук для работы с локализацией:
// src/app/components/app-layout-header/useAppLayoutHeader.ts
import { useMemo } from 'react';
import { localization } from '@/shared/plugins/i18n';
export default function useAppLayoutHeader() {
const languagesLoading = useMemo(() =>
localization.languagesLoading.value || localization.messagesLoading.value,
[localization.languagesLoading.value, localization.messagesLoading.value]
);
const selectLang = (value: string) => {
localization.changeLanguage(value);
};
return {
languagesLoading,
selectLang
};
}- Используем хук в React компоненте:
// src/app/components/app-layout-header/AppLayoutHeader.tsx
import React from 'react';
import useAppLayoutHeader from './useAppLayoutHeader';
const AppLayoutHeader: React.FC = () => {
const { languagesLoading, selectLang } = useAppLayoutHeader();
return (
<div>
{/* Ваш UI */}
<select onChange={(e) => selectLang(e.target.value)}>
<option value="ru">Русский</option>
<option value="uz">O'zbek</option>
<option value="en">English</option>
</select>
{languagesLoading && <div>Загрузка...</div>}
</div>
);
};
export default AppLayoutHeader;Angular
- Создаем файл переменных окружения:
// src/environments/environment.ts
export const environment = {
production: false,
i18nEnv: 'production'
};- Создаем сервис для глобальной конфигурации:
// src/app/shared/config/global-config.service.ts
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class GlobalConfigService {
public readonly i18n = {
env: environment.i18nEnv
};
}- Создаем сервис локализации:
// src/app/shared/services/i18n.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Localization } from '@uzum-tech/i18n';
import { GlobalConfigService } from '../config/global-config.service';
@Injectable({
providedIn: 'root'
})
export class I18nService {
private messages: Record<string, Record<string, string>> = {};
private currentLocale$ = new BehaviorSubject('ru');
private missingHandler: ((locale: string, key: string) => void) | null = null;
private i18n = {
global: {
get locale() {
return this.currentLocale$.value;
},
setLocaleMessage: (language: string, msgs: Record<string, string>) => {
this.messages[language] = msgs;
},
mergeLocaleMessage: (language: string, msgs: Record<string, string>) => {
this.messages[language] = { ...this.messages[language], ...msgs };
},
setMissingHandler: (handler: (locale: string, key: string) => void) => {
this.missingHandler = handler;
}
}
};
public readonly localization: Localization;
constructor(private globalConfig: GlobalConfigService) {
// Создаем Angular-совместимую ref функцию
const createRef = <T>(value: T) => {
const subject = new BehaviorSubject(value);
return {
get value() {
return subject.value;
},
set value(newValue: T) {
subject.next(newValue);
}
};
};
this.localization = new Localization(this.i18n, {
projectKey: 'YOUR_PROJECT_KEY',
env: this.globalConfig.i18n.env,
ref: createRef
});
}
get languagesLoading() {
return this.localization.languagesLoading.value || this.localization.messagesLoading.value;
}
selectLang(value: string) {
this.localization.changeLanguage(value);
}
}- Создаем компонент для работы с локализацией:
// src/app/components/app-layout-header/app-layout-header.component.ts
import { Component } from '@angular/core';
import { I18nService } from '@/shared/services/i18n.service';
@Component({
selector: 'app-layout-header',
template: `
<div>
<!-- Ваш UI -->
<select (change)="selectLang($event)">
<option value="ru">Русский</option>
<option value="uz">O'zbek</option>
<option value="en">English</option>
</select>
<div *ngIf="languagesLoading">Загрузка...</div>
</div>
`
})
export class AppLayoutHeaderComponent {
constructor(private i18nService: I18nService) {}
get languagesLoading() {
return this.i18nService.languagesLoading;
}
selectLang(event: Event) {
const target = event.target as HTMLSelectElement;
this.i18nService.selectLang(target.value);
}
}Preact
- Создаем файл переменных окружения:
# .env
PREACT_APP_I18N_ENV=production- Создаем файл конфигурации:
// src/shared/config/global-config.ts
export const globalConfig = {
i18n: {
env: process.env.PREACT_APP_I18N_ENV
}
} as const;- Создаем файл локализации:
// src/shared/plugins/i18n.ts
import { signal } from '@preact/signals';
import { Localization } from '@uzum-tech/i18n';
import { globalConfig } from '../config/global-config';
// Создаем Preact-совместимый i18n объект
const createPreactI18n = () => {
const messages: Record<string, Record<string, string>> = {};
const currentLocale = signal('ru');
let missingHandler: ((locale: string, key: string) => void) | null = null;
return {
global: {
get locale() {
return currentLocale.value;
},
setLocaleMessage: (language: string, msgs: Record<string, string>) => {
messages[language] = msgs;
},
mergeLocaleMessage: (language: string, msgs: Record<string, string>) => {
messages[language] = { ...messages[language], ...msgs };
},
setMissingHandler: (handler: (locale: string, key: string) => void) => {
missingHandler = handler;
}
}
};
};
// Создаем ref функцию для Preact
const createRef = <T>(value: T) => {
const sig = signal(value);
return {
get value() {
return sig.value;
},
set value(newValue: T) {
sig.value = newValue;
}
};
};
const i18n = createPreactI18n();
export const localization = new Localization(i18n, {
projectKey: 'YOUR_PROJECT_KEY',
env: globalConfig.i18n.env,
ref: createRef
});
export default i18n;- Создаем хук для работы с локализацией:
// src/app/components/app-layout-header/useAppLayoutHeader.ts
import { computed } from '@preact/signals';
import { localization } from '@/shared/plugins/i18n';
export default function useAppLayoutHeader() {
const languagesLoading = computed(() =>
localization.languagesLoading.value || localization.messagesLoading.value
);
const selectLang = (value: string) => {
localization.changeLanguage(value);
};
return {
languagesLoading,
selectLang
};
}- Используем хук в Preact компоненте:
// src/app/components/app-layout-header/AppLayoutHeader.tsx
import { h } from 'preact';
import useAppLayoutHeader from './useAppLayoutHeader';
const AppLayoutHeader = () => {
const { languagesLoading, selectLang } = useAppLayoutHeader();
return (
<div>
{/* Ваш UI */}
<select onChange={(e) => selectLang((e.target as HTMLSelectElement).value)}>
<option value="ru">Русский</option>
<option value="uz">O'zbek</option>
<option value="en">English</option>
</select>
{languagesLoading.value && <div>Загрузка...</div>}
</div>
);
};
export default AppLayoutHeader;Svelte
- Создаем файл переменных окружения:
# .env
VITE_I18N_ENV=production- Создаем файл конфигурации:
// src/shared/config/global-config.ts
export const globalConfig = {
i18n: {
env: import.meta.env.VITE_I18N_ENV
}
} as const;- Создаем файл локализации:
// src/shared/plugins/i18n.ts
import { writable } from 'svelte/store';
import { Localization } from '@uzum-tech/i18n';
import { globalConfig } from '../config/global-config';
// Создаем Svelte-совместимый i18n объект
const createSvelteI18n = () => {
const messages: Record<string, Record<string, string>> = {};
const currentLocale = writable('ru');
let missingHandler: ((locale: string, key: string) => void) | null = null;
return {
global: {
locale: currentLocale,
setLocaleMessage: (language: string, msgs: Record<string, string>) => {
messages[language] = msgs;
},
mergeLocaleMessage: (language: string, msgs: Record<string, string>) => {
messages[language] = { ...messages[language], ...msgs };
},
setMissingHandler: (handler: (locale: string, key: string) => void) => {
missingHandler = handler;
}
}
};
};
// Создаем ref функцию для Svelte
const createRef = <T>(value: T) => {
const store = writable(value);
return {
get value() {
let currentValue: T;
const unsubscribe = store.subscribe(val => currentValue = val);
unsubscribe();
return currentValue!;
},
set value(newValue: T) {
store.set(newValue);
}
};
};
const i18n = createSvelteI18n();
export const localization = new Localization(i18n, {
projectKey: 'YOUR_PROJECT_KEY',
env: globalConfig.i18n.env,
ref: createRef
});
export default i18n;- Создаем store для работы с локализацией:
// src/app/stores/i18n.ts
import { derived } from 'svelte/store';
import { localization } from '@/shared/plugins/i18n';
export const languagesLoading = derived(
[localization.languagesLoading, localization.messagesLoading],
([langLoading, msgLoading]) => langLoading || msgLoading
);
export const selectLang = (value: string) => {
localization.changeLanguage(value);
};- Используем store в Svelte компоненте:
<!-- src/app/components/AppLayoutHeader.svelte -->
<script lang="ts">
import { languagesLoading, selectLang } from '@/app/stores/i18n';
const handleLanguageChange = (event: Event) => {
const target = event.target as HTMLSelectElement;
selectLang(target.value);
};
</script>
<div>
<!-- Ваш UI -->
<select on:change={handleLanguageChange}>
<option value="ru">Русский</option>
<option value="uz">O'zbek</option>
<option value="en">English</option>
</select>
{#if $languagesLoading}
<div>Загрузка...</div>
{/if}
</div>Описание типов
Интерфейс LocalizationOptions
| Название | Тип | Значение по умолчанию | Описание |
| -- | -- | -- | -- |
| projectKey | string | Отсутствует. Необходимо передать. | Ключ проекта, определяется при создании проекта через админку. Для консистентности придерживаемся kebab-case регистра, латиницы и чисел по необходимости. |
| env | 'local' \| 'development' \| 'staging' \| 'production' | 'production' | Все стенды кроме 'production' доступны только через VPN. Каждый стенд изолирован, как и данные в них. |
| defaultLanguageCode | string | undefined | Язык по умолчанию. Используется как запасной вариант, если язык не удалось определить из URL пути или localStorage. Приоритетность выбора языка при инициализации: URL путь → localStorage → defaultLanguageCode → язык по умолчанию в проекте → язык браузера. |
| ref | <T>(value: T) => { value: T (get/set) } | undefined | Функция, создающая реактивное состояние с геттером и сеттером. Нужно чтобы вы могли реактивно следить за состояниями: languagesLoading, messagesLoading |
Интерфейс I18n
| Название | Тип | Значение по умолчанию | Описание |
| -- | -- | -- | -- |
| global | I18nGlobal | Отсутствует. Необходимо передать. | Объект содержащий свойства и методы для работы с переводами и языками. |
Интерфейс I18nGlobal
| Название | Тип | Значение по умолчанию | Описание |
| -- | -- | -- | -- |
| locale | string \| { value: string } | Отсутствует. Необходимо передать. | Выбранный язык. |
| setLocaleMessage | (language: string, messages: Record<string, string>) => void | Отсутствует. Необходимо передать. | Метод устанавливающий все ключи языка. |
| mergeLocaleMessage | (language: string, messages: Record<string, string>) => void | Отсутствует. Необходимо передать. | Метод добавляющий новые ключи в язык. |
| setMissingHandler | (handler: I18nMissingHandler) => void | Отсутствует. Необходимо передать. | Метод устанавливающий коллбэк обрабатывающий пустые значения. |
