feat(i18n): complete Russian localization, centralized dictionary, and translation cleanliness test

This commit is contained in:
ochenstarik 2026-09-11 11:26:51 +07:00
parent 30722da89e
commit 6681e0ef56
8 changed files with 246 additions and 29 deletions

View file

@ -124,7 +124,7 @@ export const SYSTEM_MODULES: ModuleDefinition[] = [
}, },
{ {
id: 'helpdesk', id: 'helpdesk',
label: 'HelpDesk', label: 'Поддержка',
iconName: 'Headset', iconName: 'Headset',
route: '/helpdesk', route: '/helpdesk',
requiredCapability: 'helpdesk', requiredCapability: 'helpdesk',
@ -200,7 +200,7 @@ export const SYSTEM_MODULES: ModuleDefinition[] = [
route: '/automation', route: '/automation',
requiredCapability: 'automation', requiredCapability: 'automation',
sections: [ sections: [
{ id: 'scenarios', label: 'Сценарии & Workflows', route: '/automation' }, { id: 'scenarios', label: 'Сценарии автоматизации', route: '/automation' },
{ id: 'builder', label: 'Конструктор цепочек', route: '/automation/builder' }, { id: 'builder', label: 'Конструктор цепочек', route: '/automation/builder' },
{ id: 'triggers', label: 'Триггеры событий', route: '/automation/triggers' }, { id: 'triggers', label: 'Триггеры событий', route: '/automation/triggers' },
{ id: 'history', label: 'Лог выполнения', route: '/automation/history' }, { id: 'history', label: 'Лог выполнения', route: '/automation/history' },

View file

@ -23,15 +23,15 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
owner: { owner: {
id: 'owner', id: 'owner',
label: 'Владелец бизнеса', label: 'Владелец бизнеса',
badge: 'Owner', badge: 'Владелец',
description: 'Полный доступ ко всем модулям, финансовой аналитике, настройкам и AI-центру', description: 'Полный доступ ко всем модулям, финансовой аналитике, настройкам и ИИ-центру',
allowedModules: ['*'], allowedModules: ['*'],
defaultRoute: '/', defaultRoute: '/',
}, },
administrator: { administrator: {
id: 'administrator', id: 'administrator',
label: 'Администратор', label: 'Администратор',
badge: 'Admin', badge: 'Администратор',
description: 'Управление процессами, персоналом, точками, правами и операционной деятельностью', description: 'Управление процессами, персоналом, точками, правами и операционной деятельностью',
allowedModules: ['*'], allowedModules: ['*'],
defaultRoute: '/', defaultRoute: '/',
@ -39,7 +39,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
manager: { manager: {
id: 'manager', id: 'manager',
label: 'Управляющий', label: 'Управляющий',
badge: 'Manager', badge: 'Управляющий',
description: 'Операции, смены, заказы, доставка, склад, контроль персонала и операционная аналитика', description: 'Операции, смены, заказы, доставка, склад, контроль персонала и операционная аналитика',
allowedModules: ['dashboard', 'operations', 'crm', 'sales', 'inventory', 'employees', 'tasks', 'communications', 'analytics'], allowedModules: ['dashboard', 'operations', 'crm', 'sales', 'inventory', 'employees', 'tasks', 'communications', 'analytics'],
defaultRoute: '/operations', defaultRoute: '/operations',
@ -47,7 +47,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
cashier: { cashier: {
id: 'cashier', id: 'cashier',
label: 'Кассир / Оператор', label: 'Кассир / Оператор',
badge: 'POS', badge: 'Кассир',
description: 'Кассовый терминал, оформление заказов, прием оплаты, клиентская база', description: 'Кассовый терминал, оформление заказов, прием оплаты, клиентская база',
allowedModules: ['operations', 'sales', 'crm'], allowedModules: ['operations', 'sales', 'crm'],
defaultRoute: '/operations/pos', defaultRoute: '/operations/pos',
@ -55,7 +55,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
cook: { cook: {
id: 'cook', id: 'cook',
label: 'Повар / Шеф-повар', label: 'Повар / Шеф-повар',
badge: 'KDS', badge: 'Кухня',
description: 'Кухонный дисплей (KDS), отметка готовности блюд, стоп-лист', description: 'Кухонный дисплей (KDS), отметка готовности блюд, стоп-лист',
allowedModules: ['operations'], allowedModules: ['operations'],
defaultRoute: '/operations/kds', defaultRoute: '/operations/kds',
@ -63,7 +63,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
courier: { courier: {
id: 'courier', id: 'courier',
label: 'Курьер / Доставка', label: 'Курьер / Доставка',
badge: 'Courier', badge: 'Курьер',
description: 'Мобильный экран доставки: маршруты, заказы, адрес клиента, отметка вручения', description: 'Мобильный экран доставки: маршруты, заказы, адрес клиента, отметка вручения',
allowedModules: ['operations'], allowedModules: ['operations'],
defaultRoute: '/operations/courier', defaultRoute: '/operations/courier',
@ -71,7 +71,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
specialist: { specialist: {
id: 'specialist', id: 'specialist',
label: 'Специалист / Мастер', label: 'Специалист / Мастер',
badge: 'Service', badge: 'Мастер',
description: 'Журнал записи, заказ-наряды постов, расписание смен, услуги', description: 'Журнал записи, заказ-наряды постов, расписание смен, услуги',
allowedModules: ['operations', 'crm', 'tasks'], allowedModules: ['operations', 'crm', 'tasks'],
defaultRoute: '/operations/work-orders', defaultRoute: '/operations/work-orders',
@ -79,15 +79,15 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
marketer: { marketer: {
id: 'marketer', id: 'marketer',
label: 'Маркетолог', label: 'Маркетолог',
badge: 'Marketing', badge: 'Маркетолог',
description: 'Маркетинговые кампании, CRM сегменты, сайты/CMS, рассылки, сквозная аналитика, AI', description: 'Маркетинговые кампании, CRM сегменты, сайты/CMS, рассылки, сквозная аналитика, ИИ',
allowedModules: ['dashboard', 'crm', 'marketing', 'cms', 'communications', 'analytics', 'ai'], allowedModules: ['dashboard', 'crm', 'marketing', 'cms', 'communications', 'analytics', 'ai'],
defaultRoute: '/marketing', defaultRoute: '/marketing',
}, },
warehouse: { warehouse: {
id: 'warehouse', id: 'warehouse',
label: 'Складской оператор', label: 'Складской оператор',
badge: 'Warehouse', badge: 'Склад',
description: 'Приходные накладные, списания, партии, инвентаризация и поставщики', description: 'Приходные накладные, списания, партии, инвентаризация и поставщики',
allowedModules: ['operations', 'inventory', 'tasks'], allowedModules: ['operations', 'inventory', 'tasks'],
defaultRoute: '/operations/inventory', defaultRoute: '/operations/inventory',
@ -95,7 +95,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
support: { support: {
id: 'support', id: 'support',
label: 'Специалист поддержки', label: 'Специалист поддержки',
badge: 'Support', badge: 'Поддержка',
description: 'Очередь тикетов, омниканальные чаты, база знаний и SLA', description: 'Очередь тикетов, омниканальные чаты, база знаний и SLA',
allowedModules: ['helpdesk', 'communications', 'crm'], allowedModules: ['helpdesk', 'communications', 'crm'],
defaultRoute: '/helpdesk', defaultRoute: '/helpdesk',

View file

@ -59,9 +59,9 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
restaurant_delivery_only: { restaurant_delivery_only: {
id: 'restaurant_delivery_only', id: 'restaurant_delivery_only',
name: 'Ресторан: Только Доставка / Dark Kitchen', name: 'Ресторан: Только Доставка / Кухня доставки',
label: 'Суши & Гёдза Express (Dark Kitchen)', label: 'Суши & Гёдза Express (Кухня доставки)',
category: 'Общепит / Dark Kitchen', category: 'Общепит / Кухня доставки',
companyName: 'Суши & Гёдза Express', companyName: 'Суши & Гёдза Express',
locations: ['Красный пр-т 186а (Кухня 1)'], locations: ['Красный пр-т 186а (Кухня 1)'],
capabilities: { capabilities: {
@ -100,7 +100,7 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
], ],
terminology: { terminology: {
itemLabel: 'Позиция доставки', itemLabel: 'Позиция доставки',
itemsLabel: 'Ассортимент Dark Kitchen', itemsLabel: 'Ассортимент кухни доставки',
bookingResourceLabel: 'Линия сборки', bookingResourceLabel: 'Линия сборки',
orderSourceLabel: 'Доставка / Агрегаторы', orderSourceLabel: 'Доставка / Агрегаторы',
clientIdentifierLabel: 'Получатель заказа', clientIdentifierLabel: 'Получатель заказа',
@ -113,8 +113,8 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
retail: { retail: {
id: 'retail', id: 'retail',
name: 'Розничная торговля / Retail', name: 'Розничная торговля',
label: 'Krona Retail & Store', label: 'Krona Розничная торговля & Store',
category: 'Торговля', category: 'Торговля',
companyName: 'Krona Concept Store', companyName: 'Krona Concept Store',
locations: ['Флагманский магазин — Ленина 12', 'Островок в ТЦ'], locations: ['Флагманский магазин — Ленина 12', 'Островок в ТЦ'],
@ -579,8 +579,8 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
custom_meta: { custom_meta: {
id: 'custom_meta', id: 'custom_meta',
name: 'Кастомный профиль / Meta-profile', name: 'Кастомный профиль / Настраиваемый профиль',
label: 'Универсальный Business OS (Все модули)', label: 'Универсальная Бизнес-платформа (Все модули)',
category: 'Конфигурируемый', category: 'Конфигурируемый',
companyName: 'Бизнес-экосистема Крона', companyName: 'Бизнес-экосистема Крона',
locations: ['Главный офис', 'Локация 2'], locations: ['Главный офис', 'Локация 2'],

View file

@ -0,0 +1,91 @@
import { describe, it, expect } from 'vitest';
import { SYSTEM_ROLES } from '../../core/permissions/types';
import { BUSINESS_PROFILES } from '../../core/profiles/profiles';
import { SYSTEM_MODULES } from '../../core/modules/registry';
import { LOCALIZATION_DICTIONARY } from '../../shared/lib/localizationDictionary';
describe('Localization Cleanliness Test Suite', () => {
it('verifies all SYSTEM_ROLES badges and labels are strictly localized in Russian', () => {
const englishBadges = [
'Owner',
'Admin',
'Manager',
'POS',
'KDS',
'Courier',
'Service',
'Marketing',
'Warehouse',
'Support',
];
Object.values(SYSTEM_ROLES).forEach((role) => {
// Must not use any legacy English badge
expect(englishBadges).not.toContain(role.badge);
// Badge and label must contain Cyrillic characters
expect(role.badge).toMatch(/[а-яА-ЯёЁ]/);
expect(role.label).toMatch(/[а-яА-ЯёЁ]/);
expect(role.description).toMatch(/[а-яА-ЯёЁ]/);
});
// Verify expected Russian badges exist
expect(SYSTEM_ROLES.owner.badge).toBe('Владелец');
expect(SYSTEM_ROLES.administrator.badge).toBe('Администратор');
expect(SYSTEM_ROLES.manager.badge).toBe('Управляющий');
expect(SYSTEM_ROLES.cashier.badge).toBe('Кассир');
expect(SYSTEM_ROLES.cook.badge).toBe('Кухня');
expect(SYSTEM_ROLES.courier.badge).toBe('Курьер');
expect(SYSTEM_ROLES.specialist.badge).toBe('Мастер');
expect(SYSTEM_ROLES.marketer.badge).toBe('Маркетолог');
expect(SYSTEM_ROLES.warehouse.badge).toBe('Склад');
expect(SYSTEM_ROLES.support.badge).toBe('Поддержка');
});
it('verifies BUSINESS_PROFILES contains zero legacy untranslated markers', () => {
const forbiddenSubstrings = ['Dark Kitchen', 'Meta-profile', 'Business OS'];
Object.values(BUSINESS_PROFILES).forEach((profile) => {
forbiddenSubstrings.forEach((forbidden) => {
expect(profile.name).not.toContain(forbidden);
expect(profile.label).not.toContain(forbidden);
});
});
expect(BUSINESS_PROFILES.restaurant_delivery_only.name).toContain('Кухня доставки');
expect(BUSINESS_PROFILES.retail.name).toContain('Розничная торговля');
expect(BUSINESS_PROFILES.custom_meta.name).toContain('Настраиваемый профиль');
expect(BUSINESS_PROFILES.custom_meta.label).toContain('Бизнес-платформа');
});
it('verifies SYSTEM_MODULES labels and sections have clean Russian localization', () => {
const allowedAcronyms = ['CRM', 'CMS'];
SYSTEM_MODULES.forEach((module) => {
if (!allowedAcronyms.includes(module.label)) {
expect(module.label).toMatch(/[а-яА-ЯёЁ]/);
}
module.sections.forEach((section) => {
expect(section.label).not.toContain('& Workflows');
});
});
const aiModule = SYSTEM_MODULES.find((m) => m.id === 'ai');
expect(aiModule?.label).toBe('ИИ-центр');
const autoModule = SYSTEM_MODULES.find((m) => m.id === 'automation');
const scenariosSection = autoModule?.sections.find((s) => s.id === 'scenarios');
expect(scenariosSection?.label).toBe('Сценарии автоматизации');
});
it('verifies LOCALIZATION_DICTIONARY contains standard Russian keys and values', () => {
expect(LOCALIZATION_DICTIONARY.maturityLevels.pilot1.label).toBe('Пилотный режим');
expect(LOCALIZATION_DICTIONARY.maturityLevels.target.label).toBe('Интерфейс подготовлен');
expect(LOCALIZATION_DICTIONARY.maturityLevels.demo.label).toBe('Только демонстрация');
expect(LOCALIZATION_DICTIONARY.maturityLevels.planned.label).toBe('Запланирован');
expect(LOCALIZATION_DICTIONARY.roles.owner.badge).toBe('Владелец');
expect(LOCALIZATION_DICTIONARY.roles.cashier.badge).toBe('Кассир');
expect(LOCALIZATION_DICTIONARY.modules.ai).toBe('ИИ-центр');
});
});

View file

@ -13,7 +13,7 @@ describe('Phase E: Target Prototype & Maturity Badges Tests', () => {
render(<MaturityBadge level="pilot1" />); render(<MaturityBadge level="pilot1" />);
const badge = screen.getByTestId('maturity-badge-pilot1'); const badge = screen.getByTestId('maturity-badge-pilot1');
expect(badge).toBeInTheDocument(); expect(badge).toBeInTheDocument();
expect(badge).toHaveTextContent('Pilot 1'); expect(badge).toHaveTextContent('Пилотный режим');
expect(badge.className).toContain('text-emerald-800'); expect(badge.className).toContain('text-emerald-800');
}); });
@ -21,7 +21,7 @@ describe('Phase E: Target Prototype & Maturity Badges Tests', () => {
render(<MaturityBadge level="target" />); render(<MaturityBadge level="target" />);
const badge = screen.getByTestId('maturity-badge-target'); const badge = screen.getByTestId('maturity-badge-target');
expect(badge).toBeInTheDocument(); expect(badge).toBeInTheDocument();
expect(badge).toHaveTextContent('Target prototype'); expect(badge).toHaveTextContent('Интерфейс подготовлен');
expect(badge.className).toContain('text-sky-800'); expect(badge.className).toContain('text-sky-800');
}); });
@ -29,7 +29,7 @@ describe('Phase E: Target Prototype & Maturity Badges Tests', () => {
render(<MaturityBadge level="demo" />); render(<MaturityBadge level="demo" />);
const badge = screen.getByTestId('maturity-badge-demo'); const badge = screen.getByTestId('maturity-badge-demo');
expect(badge).toBeInTheDocument(); expect(badge).toBeInTheDocument();
expect(badge).toHaveTextContent('Demo only'); expect(badge).toHaveTextContent('Только демонстрация');
expect(badge.className).toContain('text-purple-800'); expect(badge.className).toContain('text-purple-800');
}); });

View file

@ -0,0 +1,126 @@
/**
* Centralized Russian localization dictionary for Krona Universal Business OS.
* Contains standard localized names, badges, roles, and status mappings.
*/
export const LOCALIZATION_DICTIONARY = {
roles: {
owner: {
label: 'Владелец бизнеса',
badge: 'Владелец',
description: 'Полный доступ ко всем модулям, финансовой аналитике, настройкам и ИИ-центру',
},
administrator: {
label: 'Администратор',
badge: 'Администратор',
description: 'Управление процессами, персоналом, точками, правами и операционной деятельностью',
},
manager: {
label: 'Управляющий',
badge: 'Управляющий',
description: 'Операции, смены, заказы, доставка, склад, контроль персонала и операционная аналитика',
},
cashier: {
label: 'Кассир / Оператор',
badge: 'Кассир',
description: 'Кассовый терминал, оформление заказов, прием оплаты, клиентская база',
},
cook: {
label: 'Повар / Шеф-повар',
badge: 'Кухня',
description: 'Кухонный дисплей (KDS), отметка готовности блюд, стоп-лист',
},
courier: {
label: 'Курьер / Доставка',
badge: 'Курьер',
description: 'Мобильный экран доставки: маршруты, заказы, адрес клиента, отметка вручения',
},
specialist: {
label: 'Специалист / Мастер',
badge: 'Мастер',
description: 'Журнал записи, заказ-наряды постов, расписание смен, услуги',
},
marketer: {
label: 'Маркетолог',
badge: 'Маркетолог',
description: 'Маркетинговые кампании, CRM сегменты, сайты/CMS, рассылки, сквозная аналитика, ИИ',
},
warehouse: {
label: 'Складской оператор',
badge: 'Склад',
description: 'Приходные накладные, списания, партии, инвентаризация и поставщики',
},
support: {
label: 'Специалист поддержки',
badge: 'Поддержка',
description: 'Очередь тикетов, омниканальные чаты, база знаний и SLA',
},
},
maturityLevels: {
pilot1: {
label: 'Пилотный режим',
description: 'Готово к пилоту (IndexedDB, транзакционный контур)',
},
target: {
label: 'Интерфейс подготовлен',
description: 'Целевой прототип (интерактивный макет с бизнес-логикой)',
},
demo: {
label: 'Только демонстрация',
description: 'Демонстрационный режим (симуляция)',
},
planned: {
label: 'Запланирован',
description: 'В разработке (дорожная карта Q4)',
},
},
profiles: {
restaurant_hall_delivery: 'Ресторан: Зал + Доставка',
restaurant_delivery_only: 'Ресторан: Только Доставка / Кухня доставки',
retail: 'Розничная торговля',
services_booking: 'Услуги и запись',
dark_kitchen: 'Кухня доставки',
custom_meta: 'Настраиваемый профиль',
},
modules: {
dashboard: 'Главная',
operations: 'Операции',
crm: 'CRM',
sales: 'Продажи',
inventory: 'Склад',
procurement: 'Закупки',
finance: 'Финансы',
employees: 'Сотрудники',
communications: 'Коммуникации',
helpdesk: 'Поддержка клиентов',
tasks: 'Задачи & Проекты',
cms: 'Сайты',
marketing: 'Маркетинг',
analytics: 'Аналитика',
automation: 'Автоматизация',
ai: 'ИИ-центр',
apps: 'Приложения',
settings: 'Настройки',
},
common: {
create: 'Создать',
edit: 'Редактировать',
delete: 'Удалить',
save: 'Сохранить',
cancel: 'Отмена',
reset: 'Сбросить',
apply: 'Применить',
filter: 'Фильтры',
search: 'Поиск',
close: 'Закрыть',
export: 'Экспорт',
import: 'Импорт',
all: 'Все',
details: 'О модуле',
open: 'Перейти',
},
} as const;

View file

@ -20,21 +20,21 @@ export const MaturityBadge: React.FC<MaturityBadgeProps> = ({
}) => { }) => {
const config = { const config = {
pilot1: { pilot1: {
label: 'Pilot 1', label: 'Пилотный режим',
description: 'Готово к пилоту (IndexedDB, транзакционный контур)', description: 'Готово к пилоту (IndexedDB, транзакционный контур)',
badgeClass: 'bg-emerald-50 text-emerald-800 border-emerald-300 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-700', badgeClass: 'bg-emerald-50 text-emerald-800 border-emerald-300 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-700',
icon: CheckCircle2, icon: CheckCircle2,
iconClass: 'text-emerald-600 dark:text-emerald-400', iconClass: 'text-emerald-600 dark:text-emerald-400',
}, },
target: { target: {
label: 'Target prototype', label: 'Интерфейс подготовлен',
description: 'Целевой прототип (интерактивный макет с бизнес-логикой)', description: 'Целевой прототип (интерактивный макет с бизнес-логикой)',
badgeClass: 'bg-sky-50 text-sky-800 border-sky-300 dark:bg-sky-950/40 dark:text-sky-300 dark:border-sky-700', badgeClass: 'bg-sky-50 text-sky-800 border-sky-300 dark:bg-sky-950/40 dark:text-sky-300 dark:border-sky-700',
icon: Layers, icon: Layers,
iconClass: 'text-sky-600 dark:text-sky-400', iconClass: 'text-sky-600 dark:text-sky-400',
}, },
demo: { demo: {
label: 'Demo only', label: 'Только демонстрация',
description: 'Демонстрационный режим (симуляция)', description: 'Демонстрационный режим (симуляция)',
badgeClass: 'bg-purple-50 text-purple-800 border-purple-300 dark:bg-purple-950/40 dark:text-purple-300 dark:border-purple-700', badgeClass: 'bg-purple-50 text-purple-800 border-purple-300 dark:bg-purple-950/40 dark:text-purple-300 dark:border-purple-700',
icon: FlaskConical, icon: FlaskConical,

File diff suppressed because one or more lines are too long