feat(i18n): complete Russian localization, centralized dictionary, and translation cleanliness test
This commit is contained in:
parent
30722da89e
commit
6681e0ef56
8 changed files with 246 additions and 29 deletions
|
|
@ -124,7 +124,7 @@ export const SYSTEM_MODULES: ModuleDefinition[] = [
|
|||
},
|
||||
{
|
||||
id: 'helpdesk',
|
||||
label: 'HelpDesk',
|
||||
label: 'Поддержка',
|
||||
iconName: 'Headset',
|
||||
route: '/helpdesk',
|
||||
requiredCapability: 'helpdesk',
|
||||
|
|
@ -200,7 +200,7 @@ export const SYSTEM_MODULES: ModuleDefinition[] = [
|
|||
route: '/automation',
|
||||
requiredCapability: 'automation',
|
||||
sections: [
|
||||
{ id: 'scenarios', label: 'Сценарии & Workflows', route: '/automation' },
|
||||
{ id: 'scenarios', label: 'Сценарии автоматизации', route: '/automation' },
|
||||
{ id: 'builder', label: 'Конструктор цепочек', route: '/automation/builder' },
|
||||
{ id: 'triggers', label: 'Триггеры событий', route: '/automation/triggers' },
|
||||
{ id: 'history', label: 'Лог выполнения', route: '/automation/history' },
|
||||
|
|
|
|||
|
|
@ -23,15 +23,15 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
owner: {
|
||||
id: 'owner',
|
||||
label: 'Владелец бизнеса',
|
||||
badge: 'Owner',
|
||||
description: 'Полный доступ ко всем модулям, финансовой аналитике, настройкам и AI-центру',
|
||||
badge: 'Владелец',
|
||||
description: 'Полный доступ ко всем модулям, финансовой аналитике, настройкам и ИИ-центру',
|
||||
allowedModules: ['*'],
|
||||
defaultRoute: '/',
|
||||
},
|
||||
administrator: {
|
||||
id: 'administrator',
|
||||
label: 'Администратор',
|
||||
badge: 'Admin',
|
||||
badge: 'Администратор',
|
||||
description: 'Управление процессами, персоналом, точками, правами и операционной деятельностью',
|
||||
allowedModules: ['*'],
|
||||
defaultRoute: '/',
|
||||
|
|
@ -39,7 +39,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
manager: {
|
||||
id: 'manager',
|
||||
label: 'Управляющий',
|
||||
badge: 'Manager',
|
||||
badge: 'Управляющий',
|
||||
description: 'Операции, смены, заказы, доставка, склад, контроль персонала и операционная аналитика',
|
||||
allowedModules: ['dashboard', 'operations', 'crm', 'sales', 'inventory', 'employees', 'tasks', 'communications', 'analytics'],
|
||||
defaultRoute: '/operations',
|
||||
|
|
@ -47,7 +47,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
cashier: {
|
||||
id: 'cashier',
|
||||
label: 'Кассир / Оператор',
|
||||
badge: 'POS',
|
||||
badge: 'Кассир',
|
||||
description: 'Кассовый терминал, оформление заказов, прием оплаты, клиентская база',
|
||||
allowedModules: ['operations', 'sales', 'crm'],
|
||||
defaultRoute: '/operations/pos',
|
||||
|
|
@ -55,7 +55,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
cook: {
|
||||
id: 'cook',
|
||||
label: 'Повар / Шеф-повар',
|
||||
badge: 'KDS',
|
||||
badge: 'Кухня',
|
||||
description: 'Кухонный дисплей (KDS), отметка готовности блюд, стоп-лист',
|
||||
allowedModules: ['operations'],
|
||||
defaultRoute: '/operations/kds',
|
||||
|
|
@ -63,7 +63,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
courier: {
|
||||
id: 'courier',
|
||||
label: 'Курьер / Доставка',
|
||||
badge: 'Courier',
|
||||
badge: 'Курьер',
|
||||
description: 'Мобильный экран доставки: маршруты, заказы, адрес клиента, отметка вручения',
|
||||
allowedModules: ['operations'],
|
||||
defaultRoute: '/operations/courier',
|
||||
|
|
@ -71,7 +71,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
specialist: {
|
||||
id: 'specialist',
|
||||
label: 'Специалист / Мастер',
|
||||
badge: 'Service',
|
||||
badge: 'Мастер',
|
||||
description: 'Журнал записи, заказ-наряды постов, расписание смен, услуги',
|
||||
allowedModules: ['operations', 'crm', 'tasks'],
|
||||
defaultRoute: '/operations/work-orders',
|
||||
|
|
@ -79,15 +79,15 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
marketer: {
|
||||
id: 'marketer',
|
||||
label: 'Маркетолог',
|
||||
badge: 'Marketing',
|
||||
description: 'Маркетинговые кампании, CRM сегменты, сайты/CMS, рассылки, сквозная аналитика, AI',
|
||||
badge: 'Маркетолог',
|
||||
description: 'Маркетинговые кампании, CRM сегменты, сайты/CMS, рассылки, сквозная аналитика, ИИ',
|
||||
allowedModules: ['dashboard', 'crm', 'marketing', 'cms', 'communications', 'analytics', 'ai'],
|
||||
defaultRoute: '/marketing',
|
||||
},
|
||||
warehouse: {
|
||||
id: 'warehouse',
|
||||
label: 'Складской оператор',
|
||||
badge: 'Warehouse',
|
||||
badge: 'Склад',
|
||||
description: 'Приходные накладные, списания, партии, инвентаризация и поставщики',
|
||||
allowedModules: ['operations', 'inventory', 'tasks'],
|
||||
defaultRoute: '/operations/inventory',
|
||||
|
|
@ -95,7 +95,7 @@ export const SYSTEM_ROLES: Record<SystemRoleId, RoleInfo> = {
|
|||
support: {
|
||||
id: 'support',
|
||||
label: 'Специалист поддержки',
|
||||
badge: 'Support',
|
||||
badge: 'Поддержка',
|
||||
description: 'Очередь тикетов, омниканальные чаты, база знаний и SLA',
|
||||
allowedModules: ['helpdesk', 'communications', 'crm'],
|
||||
defaultRoute: '/helpdesk',
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
|
|||
|
||||
restaurant_delivery_only: {
|
||||
id: 'restaurant_delivery_only',
|
||||
name: 'Ресторан: Только Доставка / Dark Kitchen',
|
||||
label: 'Суши & Гёдза Express (Dark Kitchen)',
|
||||
category: 'Общепит / Dark Kitchen',
|
||||
name: 'Ресторан: Только Доставка / Кухня доставки',
|
||||
label: 'Суши & Гёдза Express (Кухня доставки)',
|
||||
category: 'Общепит / Кухня доставки',
|
||||
companyName: 'Суши & Гёдза Express',
|
||||
locations: ['Красный пр-т 186а (Кухня 1)'],
|
||||
capabilities: {
|
||||
|
|
@ -100,7 +100,7 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
|
|||
],
|
||||
terminology: {
|
||||
itemLabel: 'Позиция доставки',
|
||||
itemsLabel: 'Ассортимент Dark Kitchen',
|
||||
itemsLabel: 'Ассортимент кухни доставки',
|
||||
bookingResourceLabel: 'Линия сборки',
|
||||
orderSourceLabel: 'Доставка / Агрегаторы',
|
||||
clientIdentifierLabel: 'Получатель заказа',
|
||||
|
|
@ -113,8 +113,8 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
|
|||
|
||||
retail: {
|
||||
id: 'retail',
|
||||
name: 'Розничная торговля / Retail',
|
||||
label: 'Krona Retail & Store',
|
||||
name: 'Розничная торговля',
|
||||
label: 'Krona Розничная торговля & Store',
|
||||
category: 'Торговля',
|
||||
companyName: 'Krona Concept Store',
|
||||
locations: ['Флагманский магазин — Ленина 12', 'Островок в ТЦ'],
|
||||
|
|
@ -579,8 +579,8 @@ export const BUSINESS_PROFILES: Record<string, BusinessProfile> = {
|
|||
|
||||
custom_meta: {
|
||||
id: 'custom_meta',
|
||||
name: 'Кастомный профиль / Meta-profile',
|
||||
label: 'Универсальный Business OS (Все модули)',
|
||||
name: 'Кастомный профиль / Настраиваемый профиль',
|
||||
label: 'Универсальная Бизнес-платформа (Все модули)',
|
||||
category: 'Конфигурируемый',
|
||||
companyName: 'Бизнес-экосистема Крона',
|
||||
locations: ['Главный офис', 'Локация 2'],
|
||||
|
|
|
|||
91
src/features/__tests__/localizationCleanliness.test.ts
Normal file
91
src/features/__tests__/localizationCleanliness.test.ts
Normal 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('ИИ-центр');
|
||||
});
|
||||
});
|
||||
|
|
@ -13,7 +13,7 @@ describe('Phase E: Target Prototype & Maturity Badges Tests', () => {
|
|||
render(<MaturityBadge level="pilot1" />);
|
||||
const badge = screen.getByTestId('maturity-badge-pilot1');
|
||||
expect(badge).toBeInTheDocument();
|
||||
expect(badge).toHaveTextContent('Pilot 1');
|
||||
expect(badge).toHaveTextContent('Пилотный режим');
|
||||
expect(badge.className).toContain('text-emerald-800');
|
||||
});
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ describe('Phase E: Target Prototype & Maturity Badges Tests', () => {
|
|||
render(<MaturityBadge level="target" />);
|
||||
const badge = screen.getByTestId('maturity-badge-target');
|
||||
expect(badge).toBeInTheDocument();
|
||||
expect(badge).toHaveTextContent('Target prototype');
|
||||
expect(badge).toHaveTextContent('Интерфейс подготовлен');
|
||||
expect(badge.className).toContain('text-sky-800');
|
||||
});
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ describe('Phase E: Target Prototype & Maturity Badges Tests', () => {
|
|||
render(<MaturityBadge level="demo" />);
|
||||
const badge = screen.getByTestId('maturity-badge-demo');
|
||||
expect(badge).toBeInTheDocument();
|
||||
expect(badge).toHaveTextContent('Demo only');
|
||||
expect(badge).toHaveTextContent('Только демонстрация');
|
||||
expect(badge.className).toContain('text-purple-800');
|
||||
});
|
||||
|
||||
|
|
|
|||
126
src/shared/lib/localizationDictionary.ts
Normal file
126
src/shared/lib/localizationDictionary.ts
Normal 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;
|
||||
|
|
@ -20,21 +20,21 @@ export const MaturityBadge: React.FC<MaturityBadgeProps> = ({
|
|||
}) => {
|
||||
const config = {
|
||||
pilot1: {
|
||||
label: 'Pilot 1',
|
||||
label: 'Пилотный режим',
|
||||
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',
|
||||
icon: CheckCircle2,
|
||||
iconClass: 'text-emerald-600 dark:text-emerald-400',
|
||||
},
|
||||
target: {
|
||||
label: 'Target prototype',
|
||||
label: 'Интерфейс подготовлен',
|
||||
description: 'Целевой прототип (интерактивный макет с бизнес-логикой)',
|
||||
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,
|
||||
iconClass: 'text-sky-600 dark:text-sky-400',
|
||||
},
|
||||
demo: {
|
||||
label: 'Demo only',
|
||||
label: 'Только демонстрация',
|
||||
description: 'Демонстрационный режим (симуляция)',
|
||||
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,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue