ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 50s
ci / ts / ts (packages + web) (push) Successful in 43s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m22s
- core: AuthSession keeps the access token in memory, refreshes it once for all parallel requests (single-flight) and retries them; refresh races between tabs are retried, reuse signs out; ApiError carries problem+json codes and field errors - web: TanStack Router with guards — guests go to /login, the bootstrap admin to /setup/admin, temporary passwords to /setup/password, admin pages by role - login, setup screens, Classic shell (top bar, role-based sidebar, empty player), settings: name, theme, language, password, devices - users admin: search, role filter, create with a one-time temporary password, reset, block, role change, delete as the policy allows; audit log for admins - ru/en strings for auth, admin and error codes; language and theme are saved in the profile Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import i18next, { type i18n as I18n } from 'i18next';
|
|
|
|
import enAdmin from '../locales/en/admin.json';
|
|
import enAuth from '../locales/en/auth.json';
|
|
import enCommon from '../locales/en/common.json';
|
|
import enErrors from '../locales/en/errors.json';
|
|
import ruAdmin from '../locales/ru/admin.json';
|
|
import ruAuth from '../locales/ru/auth.json';
|
|
import ruCommon from '../locales/ru/common.json';
|
|
import ruErrors from '../locales/ru/errors.json';
|
|
|
|
/** Поддерживаемые языки. Новый язык = папка в locales/ + строка здесь. */
|
|
export const SUPPORTED_LOCALES = ['ru', 'en'] as const;
|
|
export type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
export const DEFAULT_LOCALE: Locale = 'en';
|
|
|
|
export const resources = {
|
|
ru: { common: ruCommon, auth: ruAuth, admin: ruAdmin, errors: ruErrors },
|
|
en: { common: enCommon, auth: enAuth, admin: enAdmin, errors: enErrors },
|
|
} as const;
|
|
|
|
declare module 'i18next' {
|
|
interface CustomTypeOptions {
|
|
defaultNS: 'common';
|
|
resources: (typeof resources)['ru'];
|
|
}
|
|
}
|
|
|
|
/** Выбирает поддерживаемый язык из списка предпочтений (navigator.languages, настройки ОС). */
|
|
export function pickLocale(preferred: readonly string[]): Locale {
|
|
for (const tag of preferred) {
|
|
const base = tag.toLowerCase().split('-')[0];
|
|
const match = SUPPORTED_LOCALES.find((l) => l === base);
|
|
if (match) return match;
|
|
}
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
|
|
/** Создаёт и инициализирует экземпляр i18next. */
|
|
export async function createI18n(locale: Locale): Promise<I18n> {
|
|
const instance = i18next.createInstance();
|
|
await instance.init({
|
|
lng: locale,
|
|
fallbackLng: DEFAULT_LOCALE,
|
|
supportedLngs: [...SUPPORTED_LOCALES],
|
|
defaultNS: 'common',
|
|
ns: ['common', 'auth', 'admin', 'errors'],
|
|
resources,
|
|
interpolation: { escapeValue: false },
|
|
returnNull: false,
|
|
});
|
|
return instance;
|
|
}
|