Snapshot of uncommitted work: landing, bots, prod infra, partner cabinet, backoffice stage 1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
45d7512d2b
commit
d452824c62
314 files changed
+44394
-121
No files matched your search
@@ -0,0 +1,976 @@
|
||||
import { createT as createSharedT, formatAmd, formatDateTime, type Locale as SharedLocale } from '@barev/i18n';
|
||||
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
|
||||
/** Языки новой админки */
|
||||
export const BO_LOCALES = ['ru', 'en', 'hy'] as const;
|
||||
export type BoLocale = (typeof BO_LOCALES)[number];
|
||||
export const BO_LOCALE_LABELS: Record<BoLocale, string> = { ru: 'Рус', en: 'Eng', hy: 'Հայ' };
|
||||
|
||||
const ru = {
|
||||
'ui.save': 'Сохранить',
|
||||
'ui.close': 'Закрыть',
|
||||
'ui.loading': 'Загружаем…',
|
||||
'ui.error': 'Не получилось: {msg}',
|
||||
'ui.retry': 'Повторить',
|
||||
'ui.copy': 'Скопировать',
|
||||
'ui.copied': 'Скопировано',
|
||||
'ui.days': '{n} дн.',
|
||||
'ui.yes': 'Да',
|
||||
'ui.no': 'Нет',
|
||||
'ui.none': '—',
|
||||
|
||||
'err.PAYMENTS_NOT_CONFIGURED': 'Платёжная система ещё не подключена — отметьте оплату вручную.',
|
||||
'err.REFUND_TOO_BIG': 'Сумма возврата больше, чем клиент заплатил.',
|
||||
'err.ENDS_BEFORE_START': 'Подписка не может закончиться раньше, чем началась.',
|
||||
'err.CANNOT_DEMOTE_SELF': 'Нельзя отключить себя или снять с себя роль суперадмина.',
|
||||
'err.ROLE_FORBIDDEN': 'У вашей роли нет доступа к этому действию.',
|
||||
'err.NO_ROLE': 'У вашего аккаунта нет ролей в новой админке. Попросите суперадмина их выдать.',
|
||||
|
||||
'login.coverWord': 'команда',
|
||||
'login.title': 'Вход для команды',
|
||||
'login.lead': 'Подтвердите вход в рабочем боте Barev Club. Пароль не нужен.',
|
||||
'login.open': 'Подтвердить в Telegram',
|
||||
'login.qr': 'С компьютера удобнее отсканировать код телефоном.',
|
||||
'login.waiting': 'Ждём подтверждения в Telegram…',
|
||||
'login.expired': 'Ссылка устарела.',
|
||||
'login.retry': 'Получить новую ссылку',
|
||||
'login.dev': 'Войти локально',
|
||||
'login.devHint': 'Только на этом компьютере — на сервере этого блока нет.',
|
||||
'login.noTelegram': 'Нет доступа? Попросите суперадмина добавить вас в команду и прислать ссылку для привязки Telegram.',
|
||||
|
||||
'nav.dashboard': 'Дашборд',
|
||||
'nav.clients': 'Клиенты',
|
||||
'nav.chats': 'Чаты',
|
||||
'nav.orders': 'Заказы',
|
||||
'nav.providers': 'Исполнители',
|
||||
'nav.agencies': 'Агентства',
|
||||
'nav.venues': 'Заведения',
|
||||
'nav.crm': 'CRM',
|
||||
'nav.products': 'Продукты',
|
||||
'nav.journal': 'Журнал',
|
||||
'nav.team': 'Команда',
|
||||
'nav.more': 'Ещё',
|
||||
'nav.soon': 'этап {n}',
|
||||
'nav.groupWork': 'Работа',
|
||||
'nav.groupPartners': 'Партнёры',
|
||||
'nav.groupSetup': 'Настройка',
|
||||
|
||||
'role.SUPERADMIN': 'Суперадмин',
|
||||
'role.SUPPORT': 'Поддержка',
|
||||
'role.CONCIERGE': 'Консьерж',
|
||||
'role.SALES': 'Продажи',
|
||||
'roleHint.SUPERADMIN': 'Всё, включая деньги и команду',
|
||||
'roleHint.SUPPORT': 'Чат поддержки, заказы, подписки',
|
||||
'roleHint.CONCIERGE': 'Чат консьержа для подписчиков',
|
||||
'roleHint.SALES': 'CRM: заведения и агентства',
|
||||
|
||||
'shell.logout': 'Выйти',
|
||||
'shell.oldAdmin': 'Старая админка',
|
||||
'shell.linkTg': 'Привяжите Telegram — через него вы входите и получаете уведомления.',
|
||||
'shell.linkTgBtn': 'Привязать Telegram',
|
||||
|
||||
'soon.title': '{section} переезжает сюда',
|
||||
'soon.text': 'Раздел появится на этапе {n}. Пока работайте в старой админке — данные общие.',
|
||||
'soon.open': 'Открыть старую админку',
|
||||
|
||||
'dash.morning': 'Доброе утро, {name}',
|
||||
'dash.day': 'Добрый день, {name}',
|
||||
'dash.evening': 'Добрый вечер, {name}',
|
||||
'dash.attention': 'Требует внимания',
|
||||
'dash.allClear': 'Срочного нет — всё разобрано.',
|
||||
'att.unassigned': 'Заказы без исполнителя',
|
||||
'att.unreadChats': 'Непрочитанные сообщения',
|
||||
'att.webRequests': 'Заявки с сайта',
|
||||
'att.leads': 'Заявки на переезд',
|
||||
'att.appRequests': 'Заявки из приложения',
|
||||
'att.lowRatings': 'Оценки 3 и ниже за неделю',
|
||||
'att.expiring': 'Подписки истекают за 7 дней',
|
||||
'dash.money': 'Деньги',
|
||||
'dash.today': 'Сегодня',
|
||||
'dash.week': '7 дней',
|
||||
'dash.month': '30 дней',
|
||||
'dash.vsYesterday': 'вчера {sum}',
|
||||
'dash.vsPrev': 'к прошлым {period}',
|
||||
'dash.orders': 'заказы',
|
||||
'dash.subs': 'подписки',
|
||||
'dash.chart': 'Выручка по дням за 30 дней',
|
||||
'dash.subsTitle': 'Подписки',
|
||||
'dash.gifts': 'Из них подарочных: {n}',
|
||||
'dash.expiring': 'Истекают в ближайшие 7 дней',
|
||||
'dash.noExpiring': 'В ближайшую неделю ничего не истекает.',
|
||||
'dash.conversion': 'Подарок → покупка',
|
||||
'dash.conversionHint': '{paid} из {total} клиентов с подарком потом купили подписку',
|
||||
'dash.growth': 'Рост',
|
||||
'dash.newClients': 'Новые клиенты',
|
||||
'dash.prevMonth': 'за прошлые 30 дней — {n}',
|
||||
'dash.sources': 'Откуда пришли за 30 дней',
|
||||
'dash.funnel': 'Воронка за 30 дней',
|
||||
'funnel.leads': 'Заявки с сайта',
|
||||
'funnel.clients': 'Клиенты в приложении',
|
||||
'funnel.paying': 'Заплатили',
|
||||
'dash.team': 'Команда и партнёры',
|
||||
'dash.topProviders': 'Лучшие исполнители',
|
||||
'dash.bottomProviders': 'Стоит поговорить',
|
||||
'dash.topPartners': 'Заведения: сканы карты за 30 дней',
|
||||
'dash.salesSoon': 'Результаты продажников появятся вместе с CRM.',
|
||||
'dash.empty': 'Пока пусто',
|
||||
'dash.feed': 'Лента',
|
||||
'feed.subscription': '{plan} · {source}',
|
||||
'feed.payment': 'Оплачен заказ №{number}',
|
||||
'dash.ratings': '{n} оценок',
|
||||
'dash.scans': '{n} сканов',
|
||||
|
||||
'source.organic': 'Сами нашли',
|
||||
'source.deeplink': 'Ссылка агентства',
|
||||
'source.manual_code': 'Код агентства',
|
||||
'source.agency': 'Агентство «{name}»',
|
||||
'source.utm': 'Реклама: {name}',
|
||||
|
||||
'plan.BAREV_PLUS': 'Barev+',
|
||||
'plan.ELITE': 'Elite',
|
||||
'plan.none': 'Без подписки',
|
||||
'subSource.PAID': 'Покупка',
|
||||
'subSource.MANUAL': 'Подарок команды',
|
||||
'subSource.AGENCY_GIFT': 'Подарок агентства',
|
||||
'subStatus.ACTIVE': 'Активна',
|
||||
'subStatus.EXPIRED': 'Истекла',
|
||||
'subStatus.CANCELLED': 'Отменена',
|
||||
'pay.cash': 'Наличные',
|
||||
'pay.transfer': 'Перевод',
|
||||
'pay.card': 'Карта через терминал',
|
||||
'pay.link': 'Ссылка на оплату',
|
||||
'segment.MOVING': 'Переезжает',
|
||||
'segment.LIVING': 'Живёт в Армении',
|
||||
|
||||
'clients.title': 'Клиенты',
|
||||
'clients.search': 'Имя, @username, телефон или номер карты',
|
||||
'clients.found': 'Найдено: {n}',
|
||||
'clients.revenueTotal': 'принесли {sum}',
|
||||
'col.client': 'Клиент',
|
||||
'col.sub': 'Подписка',
|
||||
'col.source': 'Откуда',
|
||||
'col.revenue': 'Принёс',
|
||||
'col.since': 'С нами с',
|
||||
'filter.plan': 'Подписка',
|
||||
'filter.all': 'Все',
|
||||
'filter.any': 'С подпиской',
|
||||
'filter.none': 'Без подписки',
|
||||
'filter.subSource': 'Как получена',
|
||||
'filter.source': 'Откуда',
|
||||
'filter.agency': 'Агентство',
|
||||
'filter.tag': 'Тег',
|
||||
'filter.segment': 'Сценарий',
|
||||
'filter.paying': 'Платили',
|
||||
'filter.payingYes': 'Платили',
|
||||
'filter.payingNo': 'Ни разу не платили',
|
||||
'filter.expiring': 'Истекает за 7 дней',
|
||||
'filter.reset': 'Сбросить фильтры',
|
||||
'filter.more': 'Фильтры',
|
||||
'filter.anyValue': 'Любое',
|
||||
'sort.label': 'Порядок',
|
||||
'sort.new': 'Сначала новые',
|
||||
'sort.revenue': 'Больше принесли',
|
||||
'sort.ends': 'Скоро истекает',
|
||||
'segments.title': 'Мои выборки',
|
||||
'segments.save': 'Сохранить выборку',
|
||||
'segments.name': 'Название выборки',
|
||||
'segments.saved': 'Выборка сохранена',
|
||||
'segments.remove': 'Удалить выборку «{name}»',
|
||||
'clients.export': 'Скачать для Excel',
|
||||
'clients.exported': 'Файл скачан',
|
||||
'clients.broadcast': 'Рассылка',
|
||||
'clients.broadcastSoon': 'Рассылки по выборке появятся на этапе 5.',
|
||||
'clients.empty': 'Под эти фильтры никто не подходит.',
|
||||
'clients.emptyAll': 'Клиентов пока нет — они появятся, когда напишут боту.',
|
||||
'clients.prev': 'Назад',
|
||||
'clients.next': 'Дальше',
|
||||
'clients.page': '{from}–{to} из {total}',
|
||||
|
||||
'card.back': 'Все клиенты',
|
||||
'card.since': 'С нами с {date}',
|
||||
'card.telegram': 'Открыть в Telegram',
|
||||
'card.cardNo': 'Карта {code}',
|
||||
'card.noSub': 'Нет активной подписки',
|
||||
'card.noSubHint': 'Подарите или продайте подписку — клиент сразу получит скидки.',
|
||||
'card.until': 'до {date}',
|
||||
'card.daysLeft': 'осталось {n} дн.',
|
||||
'card.history': 'Все подписки',
|
||||
'card.grantedBy': 'начислил(а) {name}',
|
||||
'card.giftFrom': 'подарок от «{name}»',
|
||||
'card.paidVia': '{method}',
|
||||
'card.refunded': 'возврат {sum}',
|
||||
'card.cancelledAt': 'отменена {date}',
|
||||
'card.upcoming': 'начнётся {date}',
|
||||
'card.money': 'Принёс',
|
||||
'card.fromOrders': 'заказы',
|
||||
'card.fromSubs': 'подписки',
|
||||
'card.orders': 'Заказы',
|
||||
'card.noOrders': 'Заказов пока нет.',
|
||||
'card.newOrder': 'Создать заказ',
|
||||
'card.newOrderSoon': 'Заказ за клиента появится на этапе 3, вместе с разделом заказов.',
|
||||
'card.profile': 'Профиль',
|
||||
'card.source': 'Откуда пришёл',
|
||||
'card.agency': 'Агентство',
|
||||
'card.language': 'Язык',
|
||||
'card.phone': 'Телефон',
|
||||
'card.segment': 'Сценарий',
|
||||
'card.consent': 'Согласие на обработку данных',
|
||||
'card.tags': 'Теги',
|
||||
'card.addTag': 'Новый тег',
|
||||
'card.note': 'Заметка команды',
|
||||
'card.notePh': 'Клиент не видит эту заметку',
|
||||
'card.saved': 'Сохранено',
|
||||
'card.activity': 'Журнал по клиенту',
|
||||
'card.noActivity': 'Команда пока ничего не меняла.',
|
||||
'card.stats': 'Активность',
|
||||
'stat.cardUsages': 'сканов карты',
|
||||
'stat.leads': 'заявок на переезд',
|
||||
'stat.requests': 'заявок из приложения',
|
||||
'stat.concierge': 'сообщений в чате',
|
||||
|
||||
'act.gift': 'Подарить',
|
||||
'act.sell': 'Продать',
|
||||
'act.extend': 'Изменить срок',
|
||||
'act.cancel': 'Отменить',
|
||||
'act.changePlan': 'Сменить тариф',
|
||||
'act.refund': 'Вернуть деньги',
|
||||
|
||||
'audit.bo.clients.gift': 'подарил(а) подписку',
|
||||
'audit.bo.clients.sell': 'продал(а) подписку',
|
||||
'audit.bo.clients.extend': 'изменил(а) срок подписки',
|
||||
'audit.bo.clients.cancel': 'отменил(а) подписку',
|
||||
'audit.bo.clients.changePlan': 'сменил(а) тариф',
|
||||
'audit.bo.clients.refund': 'оформил(а) возврат',
|
||||
'audit.bo.clients.setTags': 'изменил(а) теги',
|
||||
'audit.bo.clients.setNote': 'изменил(а) заметку',
|
||||
|
||||
'dlg.giftTitle': 'Подарить подписку',
|
||||
'dlg.giftHint': 'Подписка будет помечена как подарок от вас.',
|
||||
'dlg.sellTitle': 'Продать подписку',
|
||||
'dlg.plan': 'Тариф',
|
||||
'dlg.period': 'Срок',
|
||||
'dlg.period1': '1 месяц',
|
||||
'dlg.period3': '3 месяца',
|
||||
'dlg.period6': '6 месяцев',
|
||||
'dlg.period12': '1 год',
|
||||
'dlg.periodCustom': 'Своё',
|
||||
'dlg.days': 'Дней',
|
||||
'dlg.amount': 'Сумма, ֏',
|
||||
'dlg.method': 'Как оплатил',
|
||||
'dlg.note': 'Комментарий для команды',
|
||||
'dlg.linkSoon': 'Ссылку на оплату включим, когда подключим платёжную систему.',
|
||||
'dlg.giftConfirm': 'Подарить',
|
||||
'dlg.sellConfirm': 'Отметить оплату',
|
||||
'dlg.extendTitle': 'Изменить срок',
|
||||
'dlg.extendHint': 'Отрицательное число сокращает срок.',
|
||||
'dlg.newEnd': 'Новая дата окончания: {date}',
|
||||
'dlg.extendConfirm': 'Сохранить срок',
|
||||
'dlg.cancelTitle': 'Отменить подписку',
|
||||
'dlg.cancelHint': 'Клиент сразу потеряет скидки и привилегии подписки.',
|
||||
'dlg.reason': 'Причина',
|
||||
'dlg.cancelConfirm': 'Отменить подписку',
|
||||
'dlg.planTitle': 'Сменить тариф',
|
||||
'dlg.planConfirm': 'Сменить тариф',
|
||||
'dlg.refundTitle': 'Вернуть деньги',
|
||||
'dlg.refundPaid': 'Оплачено {sum}, уже возвращено {back}',
|
||||
'dlg.refundCancel': 'Сразу отменить подписку',
|
||||
'dlg.refundConfirm': 'Оформить возврат',
|
||||
'done.gift': 'Подписка подарена',
|
||||
'done.sell': 'Оплата отмечена',
|
||||
'done.extend': 'Срок изменён',
|
||||
'done.cancel': 'Подписка отменена',
|
||||
'done.changePlan': 'Тариф изменён',
|
||||
'done.refund': 'Возврат оформлен',
|
||||
|
||||
'team.title': 'Команда',
|
||||
'team.lead': 'Кто работает в админке и что ему доступно. У одного человека может быть несколько ролей.',
|
||||
'team.add': 'Добавить человека',
|
||||
'team.name': 'Имя',
|
||||
'team.roles': 'Роли',
|
||||
'team.create': 'Добавить',
|
||||
'team.linkTitle': 'Ссылка для {name}',
|
||||
'team.linkHint': 'Отправьте её человеку. Он откроет рабочий бот, Telegram привяжется — и можно входить.',
|
||||
'team.tgLinked': 'Telegram привязан',
|
||||
'team.tgNotLinked': 'Telegram не привязан',
|
||||
'team.newLink': 'Ссылка для привязки',
|
||||
'team.disable': 'Отключить доступ',
|
||||
'team.enable': 'Вернуть доступ',
|
||||
'team.disabled': 'Доступ отключён',
|
||||
'team.you': 'это вы',
|
||||
'team.saved': 'Роли сохранены',
|
||||
};
|
||||
|
||||
export type BoKey = keyof typeof ru;
|
||||
type Dict = Record<BoKey, string>;
|
||||
|
||||
const en: Dict = {
|
||||
'ui.save': 'Save',
|
||||
'ui.close': 'Close',
|
||||
'ui.loading': 'Loading…',
|
||||
'ui.error': 'That didn’t work: {msg}',
|
||||
'ui.retry': 'Try again',
|
||||
'ui.copy': 'Copy',
|
||||
'ui.copied': 'Copied',
|
||||
'ui.days': '{n} d',
|
||||
'ui.yes': 'Yes',
|
||||
'ui.no': 'No',
|
||||
'ui.none': '—',
|
||||
|
||||
'err.PAYMENTS_NOT_CONFIGURED': 'Online payments aren’t connected yet — mark the payment manually.',
|
||||
'err.REFUND_TOO_BIG': 'The refund is larger than what the client paid.',
|
||||
'err.ENDS_BEFORE_START': 'A subscription can’t end before it starts.',
|
||||
'err.CANNOT_DEMOTE_SELF': 'You can’t disable yourself or remove your own super admin role.',
|
||||
'err.ROLE_FORBIDDEN': 'Your role doesn’t have access to this action.',
|
||||
'err.NO_ROLE': 'Your account has no roles in the new admin. Ask a super admin to grant them.',
|
||||
|
||||
'login.coverWord': 'team',
|
||||
'login.title': 'Team sign-in',
|
||||
'login.lead': 'Confirm sign-in in the Barev Club work bot. No password needed.',
|
||||
'login.open': 'Confirm in Telegram',
|
||||
'login.qr': 'On a computer, scan the code with your phone.',
|
||||
'login.waiting': 'Waiting for confirmation in Telegram…',
|
||||
'login.expired': 'The link has expired.',
|
||||
'login.retry': 'Get a new link',
|
||||
'login.dev': 'Local sign-in',
|
||||
'login.devHint': 'Only on this computer — the server doesn’t show this block.',
|
||||
'login.noTelegram': 'No access? Ask a super admin to add you to the team and send a Telegram link.',
|
||||
|
||||
'nav.dashboard': 'Dashboard',
|
||||
'nav.clients': 'Clients',
|
||||
'nav.chats': 'Chats',
|
||||
'nav.orders': 'Orders',
|
||||
'nav.providers': 'Providers',
|
||||
'nav.agencies': 'Agencies',
|
||||
'nav.venues': 'Venues',
|
||||
'nav.crm': 'CRM',
|
||||
'nav.products': 'Products',
|
||||
'nav.journal': 'Activity log',
|
||||
'nav.team': 'Team',
|
||||
'nav.more': 'More',
|
||||
'nav.soon': 'stage {n}',
|
||||
'nav.groupWork': 'Work',
|
||||
'nav.groupPartners': 'Partners',
|
||||
'nav.groupSetup': 'Setup',
|
||||
|
||||
'role.SUPERADMIN': 'Super admin',
|
||||
'role.SUPPORT': 'Support',
|
||||
'role.CONCIERGE': 'Concierge',
|
||||
'role.SALES': 'Sales',
|
||||
'roleHint.SUPERADMIN': 'Everything, including money and the team',
|
||||
'roleHint.SUPPORT': 'Support chat, orders, subscriptions',
|
||||
'roleHint.CONCIERGE': 'Concierge chat for subscribers',
|
||||
'roleHint.SALES': 'CRM: venues and agencies',
|
||||
|
||||
'shell.logout': 'Sign out',
|
||||
'shell.oldAdmin': 'Old admin',
|
||||
'shell.linkTg': 'Link Telegram — you sign in and get notifications through it.',
|
||||
'shell.linkTgBtn': 'Link Telegram',
|
||||
|
||||
'soon.title': '{section} is moving here',
|
||||
'soon.text': 'This section arrives in stage {n}. Until then, use the old admin — the data is shared.',
|
||||
'soon.open': 'Open the old admin',
|
||||
|
||||
'dash.morning': 'Good morning, {name}',
|
||||
'dash.day': 'Good afternoon, {name}',
|
||||
'dash.evening': 'Good evening, {name}',
|
||||
'dash.attention': 'Needs attention',
|
||||
'dash.allClear': 'Nothing urgent — all caught up.',
|
||||
'att.unassigned': 'Orders without a provider',
|
||||
'att.unreadChats': 'Unread messages',
|
||||
'att.webRequests': 'Website requests',
|
||||
'att.leads': 'Relocation requests',
|
||||
'att.appRequests': 'In-app requests',
|
||||
'att.lowRatings': 'Ratings of 3 or lower this week',
|
||||
'att.expiring': 'Subscriptions ending within 7 days',
|
||||
'dash.money': 'Money',
|
||||
'dash.today': 'Today',
|
||||
'dash.week': '7 days',
|
||||
'dash.month': '30 days',
|
||||
'dash.vsYesterday': 'yesterday {sum}',
|
||||
'dash.vsPrev': 'vs previous {period}',
|
||||
'dash.orders': 'orders',
|
||||
'dash.subs': 'subscriptions',
|
||||
'dash.chart': 'Daily revenue, last 30 days',
|
||||
'dash.subsTitle': 'Subscriptions',
|
||||
'dash.gifts': 'Gifts among them: {n}',
|
||||
'dash.expiring': 'Ending in the next 7 days',
|
||||
'dash.noExpiring': 'Nothing ends in the coming week.',
|
||||
'dash.conversion': 'Gift → purchase',
|
||||
'dash.conversionHint': '{paid} of {total} gifted clients later bought a subscription',
|
||||
'dash.growth': 'Growth',
|
||||
'dash.newClients': 'New clients',
|
||||
'dash.prevMonth': 'previous 30 days — {n}',
|
||||
'dash.sources': 'Where they came from, 30 days',
|
||||
'dash.funnel': 'Funnel, 30 days',
|
||||
'funnel.leads': 'Website requests',
|
||||
'funnel.clients': 'Clients in the app',
|
||||
'funnel.paying': 'Paid',
|
||||
'dash.team': 'Team and partners',
|
||||
'dash.topProviders': 'Top providers',
|
||||
'dash.bottomProviders': 'Worth a talk',
|
||||
'dash.topPartners': 'Venues: card scans, 30 days',
|
||||
'dash.salesSoon': 'Sales results will appear with the CRM.',
|
||||
'dash.empty': 'Nothing yet',
|
||||
'dash.feed': 'Feed',
|
||||
'feed.subscription': '{plan} · {source}',
|
||||
'feed.payment': 'Order #{number} paid',
|
||||
'dash.ratings': '{n} ratings',
|
||||
'dash.scans': '{n} scans',
|
||||
|
||||
'source.organic': 'Found us',
|
||||
'source.deeplink': 'Agency link',
|
||||
'source.manual_code': 'Agency code',
|
||||
'source.agency': 'Agency “{name}”',
|
||||
'source.utm': 'Ads: {name}',
|
||||
|
||||
'plan.BAREV_PLUS': 'Barev+',
|
||||
'plan.ELITE': 'Elite',
|
||||
'plan.none': 'No subscription',
|
||||
'subSource.PAID': 'Purchase',
|
||||
'subSource.MANUAL': 'Team gift',
|
||||
'subSource.AGENCY_GIFT': 'Agency gift',
|
||||
'subStatus.ACTIVE': 'Active',
|
||||
'subStatus.EXPIRED': 'Expired',
|
||||
'subStatus.CANCELLED': 'Cancelled',
|
||||
'pay.cash': 'Cash',
|
||||
'pay.transfer': 'Bank transfer',
|
||||
'pay.card': 'Card via terminal',
|
||||
'pay.link': 'Payment link',
|
||||
'segment.MOVING': 'Relocating',
|
||||
'segment.LIVING': 'Lives in Armenia',
|
||||
|
||||
'clients.title': 'Clients',
|
||||
'clients.search': 'Name, @username, phone or card number',
|
||||
'clients.found': 'Found: {n}',
|
||||
'clients.revenueTotal': 'brought {sum}',
|
||||
'col.client': 'Client',
|
||||
'col.sub': 'Subscription',
|
||||
'col.source': 'Source',
|
||||
'col.revenue': 'Brought',
|
||||
'col.since': 'Joined',
|
||||
'filter.plan': 'Subscription',
|
||||
'filter.all': 'All',
|
||||
'filter.any': 'Subscribed',
|
||||
'filter.none': 'Not subscribed',
|
||||
'filter.subSource': 'How received',
|
||||
'filter.source': 'Source',
|
||||
'filter.agency': 'Agency',
|
||||
'filter.tag': 'Tag',
|
||||
'filter.segment': 'Scenario',
|
||||
'filter.paying': 'Paid',
|
||||
'filter.payingYes': 'Have paid',
|
||||
'filter.payingNo': 'Never paid',
|
||||
'filter.expiring': 'Ends within 7 days',
|
||||
'filter.reset': 'Reset filters',
|
||||
'filter.more': 'Filters',
|
||||
'filter.anyValue': 'Any',
|
||||
'sort.label': 'Order',
|
||||
'sort.new': 'Newest first',
|
||||
'sort.revenue': 'Brought the most',
|
||||
'sort.ends': 'Ending soon',
|
||||
'segments.title': 'My segments',
|
||||
'segments.save': 'Save segment',
|
||||
'segments.name': 'Segment name',
|
||||
'segments.saved': 'Segment saved',
|
||||
'segments.remove': 'Delete segment “{name}”',
|
||||
'clients.export': 'Download for Excel',
|
||||
'clients.exported': 'File downloaded',
|
||||
'clients.broadcast': 'Broadcast',
|
||||
'clients.broadcastSoon': 'Segment broadcasts arrive in stage 5.',
|
||||
'clients.empty': 'No one matches these filters.',
|
||||
'clients.emptyAll': 'No clients yet — they appear once they message the bot.',
|
||||
'clients.prev': 'Back',
|
||||
'clients.next': 'Next',
|
||||
'clients.page': '{from}–{to} of {total}',
|
||||
|
||||
'card.back': 'All clients',
|
||||
'card.since': 'With us since {date}',
|
||||
'card.telegram': 'Open in Telegram',
|
||||
'card.cardNo': 'Card {code}',
|
||||
'card.noSub': 'No active subscription',
|
||||
'card.noSubHint': 'Gift or sell a subscription — the client gets discounts right away.',
|
||||
'card.until': 'until {date}',
|
||||
'card.daysLeft': '{n} d left',
|
||||
'card.history': 'All subscriptions',
|
||||
'card.grantedBy': 'granted by {name}',
|
||||
'card.giftFrom': 'gift from “{name}”',
|
||||
'card.paidVia': '{method}',
|
||||
'card.refunded': 'refund {sum}',
|
||||
'card.cancelledAt': 'cancelled {date}',
|
||||
'card.upcoming': 'starts {date}',
|
||||
'card.money': 'Brought',
|
||||
'card.fromOrders': 'orders',
|
||||
'card.fromSubs': 'subscriptions',
|
||||
'card.orders': 'Orders',
|
||||
'card.noOrders': 'No orders yet.',
|
||||
'card.newOrder': 'Create order',
|
||||
'card.newOrderSoon': 'Ordering on a client’s behalf arrives in stage 3, together with the orders section.',
|
||||
'card.profile': 'Profile',
|
||||
'card.source': 'Source',
|
||||
'card.agency': 'Agency',
|
||||
'card.language': 'Language',
|
||||
'card.phone': 'Phone',
|
||||
'card.segment': 'Scenario',
|
||||
'card.consent': 'Data processing consent',
|
||||
'card.tags': 'Tags',
|
||||
'card.addTag': 'New tag',
|
||||
'card.note': 'Team note',
|
||||
'card.notePh': 'The client doesn’t see this note',
|
||||
'card.saved': 'Saved',
|
||||
'card.activity': 'Client log',
|
||||
'card.noActivity': 'The team hasn’t changed anything yet.',
|
||||
'card.stats': 'Activity',
|
||||
'stat.cardUsages': 'card scans',
|
||||
'stat.leads': 'relocation requests',
|
||||
'stat.requests': 'in-app requests',
|
||||
'stat.concierge': 'chat messages',
|
||||
|
||||
'act.gift': 'Gift',
|
||||
'act.sell': 'Sell',
|
||||
'act.extend': 'Change term',
|
||||
'act.cancel': 'Cancel',
|
||||
'act.changePlan': 'Change plan',
|
||||
'act.refund': 'Refund',
|
||||
|
||||
'audit.bo.clients.gift': 'gifted a subscription',
|
||||
'audit.bo.clients.sell': 'sold a subscription',
|
||||
'audit.bo.clients.extend': 'changed the subscription term',
|
||||
'audit.bo.clients.cancel': 'cancelled a subscription',
|
||||
'audit.bo.clients.changePlan': 'changed the plan',
|
||||
'audit.bo.clients.refund': 'issued a refund',
|
||||
'audit.bo.clients.setTags': 'changed tags',
|
||||
'audit.bo.clients.setNote': 'changed the note',
|
||||
|
||||
'dlg.giftTitle': 'Gift a subscription',
|
||||
'dlg.giftHint': 'It will be marked as a gift from you.',
|
||||
'dlg.sellTitle': 'Sell a subscription',
|
||||
'dlg.plan': 'Plan',
|
||||
'dlg.period': 'Term',
|
||||
'dlg.period1': '1 month',
|
||||
'dlg.period3': '3 months',
|
||||
'dlg.period6': '6 months',
|
||||
'dlg.period12': '1 year',
|
||||
'dlg.periodCustom': 'Custom',
|
||||
'dlg.days': 'Days',
|
||||
'dlg.amount': 'Amount, ֏',
|
||||
'dlg.method': 'Paid by',
|
||||
'dlg.note': 'Note for the team',
|
||||
'dlg.linkSoon': 'Payment links turn on once a payment provider is connected.',
|
||||
'dlg.giftConfirm': 'Gift',
|
||||
'dlg.sellConfirm': 'Mark as paid',
|
||||
'dlg.extendTitle': 'Change term',
|
||||
'dlg.extendHint': 'A negative number shortens the term.',
|
||||
'dlg.newEnd': 'New end date: {date}',
|
||||
'dlg.extendConfirm': 'Save term',
|
||||
'dlg.cancelTitle': 'Cancel subscription',
|
||||
'dlg.cancelHint': 'The client loses subscription discounts and perks immediately.',
|
||||
'dlg.reason': 'Reason',
|
||||
'dlg.cancelConfirm': 'Cancel subscription',
|
||||
'dlg.planTitle': 'Change plan',
|
||||
'dlg.planConfirm': 'Change plan',
|
||||
'dlg.refundTitle': 'Refund',
|
||||
'dlg.refundPaid': 'Paid {sum}, already refunded {back}',
|
||||
'dlg.refundCancel': 'Cancel the subscription too',
|
||||
'dlg.refundConfirm': 'Issue refund',
|
||||
'done.gift': 'Subscription gifted',
|
||||
'done.sell': 'Payment recorded',
|
||||
'done.extend': 'Term changed',
|
||||
'done.cancel': 'Subscription cancelled',
|
||||
'done.changePlan': 'Plan changed',
|
||||
'done.refund': 'Refund issued',
|
||||
|
||||
'team.title': 'Team',
|
||||
'team.lead': 'Who works in the admin and what they can access. One person can have several roles.',
|
||||
'team.add': 'Add a person',
|
||||
'team.name': 'Name',
|
||||
'team.roles': 'Roles',
|
||||
'team.create': 'Add',
|
||||
'team.linkTitle': 'Link for {name}',
|
||||
'team.linkHint': 'Send it to them. They open the work bot, Telegram gets linked, and they can sign in.',
|
||||
'team.tgLinked': 'Telegram linked',
|
||||
'team.tgNotLinked': 'Telegram not linked',
|
||||
'team.newLink': 'Linking link',
|
||||
'team.disable': 'Disable access',
|
||||
'team.enable': 'Restore access',
|
||||
'team.disabled': 'Access disabled',
|
||||
'team.you': 'you',
|
||||
'team.saved': 'Roles saved',
|
||||
};
|
||||
|
||||
const hy: Dict = {
|
||||
'ui.save': 'Պահպանել',
|
||||
'ui.close': 'Փակել',
|
||||
'ui.loading': 'Բեռնվում է…',
|
||||
'ui.error': 'Չստացվեց՝ {msg}',
|
||||
'ui.retry': 'Կրկնել',
|
||||
'ui.copy': 'Պատճենել',
|
||||
'ui.copied': 'Պատճենված է',
|
||||
'ui.days': '{n} օր',
|
||||
'ui.yes': 'Այո',
|
||||
'ui.no': 'Ոչ',
|
||||
'ui.none': '—',
|
||||
|
||||
'err.PAYMENTS_NOT_CONFIGURED': 'Առցանց վճարումները դեռ միացված չեն․ նշեք վճարումը ձեռքով։',
|
||||
'err.REFUND_TOO_BIG': 'Վերադարձի գումարը մեծ է հաճախորդի վճարածից։',
|
||||
'err.ENDS_BEFORE_START': 'Բաժանորդագրությունը չի կարող ավարտվել սկսվելուց առաջ։',
|
||||
'err.CANNOT_DEMOTE_SELF': 'Չեք կարող անջատել ինքներդ ձեզ կամ հանել ձեր սուպերադմինի դերը։',
|
||||
'err.ROLE_FORBIDDEN': 'Ձեր դերը մուտք չունի այս գործողությանը։',
|
||||
'err.NO_ROLE': 'Ձեր հաշիվը նոր ադմինում դերեր չունի։ Խնդրեք սուպերադմինին տրամադրել։',
|
||||
|
||||
'login.coverWord': 'թիմ',
|
||||
'login.title': 'Մուտք թիմի համար',
|
||||
'login.lead': 'Հաստատեք մուտքը Barev Club-ի աշխատանքային բոտում։ Գաղտնաբառ պետք չէ։',
|
||||
'login.open': 'Հաստատել Telegram-ում',
|
||||
'login.qr': 'Համակարգչից հարմար է կոդը սկանավորել հեռախոսով։',
|
||||
'login.waiting': 'Սպասում ենք հաստատմանը Telegram-ում…',
|
||||
'login.expired': 'Հղումը հնացել է։',
|
||||
'login.retry': 'Ստանալ նոր հղում',
|
||||
'login.dev': 'Տեղային մուտք',
|
||||
'login.devHint': 'Միայն այս համակարգչում․ սերվերում այս բլոկը չկա։',
|
||||
'login.noTelegram': 'Մուտք չունե՞ք։ Խնդրեք սուպերադմինին ավելացնել ձեզ թիմում և ուղարկել Telegram-ի կապման հղում։',
|
||||
|
||||
'nav.dashboard': 'Վահանակ',
|
||||
'nav.clients': 'Հաճախորդներ',
|
||||
'nav.chats': 'Չատեր',
|
||||
'nav.orders': 'Պատվերներ',
|
||||
'nav.providers': 'Կատարողներ',
|
||||
'nav.agencies': 'Գործակալություններ',
|
||||
'nav.venues': 'Հաստատություններ',
|
||||
'nav.crm': 'CRM',
|
||||
'nav.products': 'Ապրանքներ',
|
||||
'nav.journal': 'Մատյան',
|
||||
'nav.team': 'Թիմ',
|
||||
'nav.more': 'Ավելին',
|
||||
'nav.soon': 'փուլ {n}',
|
||||
'nav.groupWork': 'Աշխատանք',
|
||||
'nav.groupPartners': 'Գործընկերներ',
|
||||
'nav.groupSetup': 'Կարգավորում',
|
||||
|
||||
'role.SUPERADMIN': 'Սուպերադմին',
|
||||
'role.SUPPORT': 'Աջակցություն',
|
||||
'role.CONCIERGE': 'Կոնսիերժ',
|
||||
'role.SALES': 'Վաճառք',
|
||||
'roleHint.SUPERADMIN': 'Ամեն ինչ, ներառյալ գումարներն ու թիմը',
|
||||
'roleHint.SUPPORT': 'Աջակցության չատ, պատվերներ, բաժանորդագրություններ',
|
||||
'roleHint.CONCIERGE': 'Կոնսիերժի չատ բաժանորդների համար',
|
||||
'roleHint.SALES': 'CRM՝ հաստատություններ և գործակալություններ',
|
||||
|
||||
'shell.logout': 'Ելք',
|
||||
'shell.oldAdmin': 'Հին ադմին',
|
||||
'shell.linkTg': 'Կապեք Telegram-ը․ դրանով եք մուտք գործում և ստանում ծանուցումներ։',
|
||||
'shell.linkTgBtn': 'Կապել Telegram-ը',
|
||||
|
||||
'soon.title': '{section} բաժինը տեղափոխվում է այստեղ',
|
||||
'soon.text': 'Բաժինը կհայտնվի {n}-րդ փուլում։ Մինչ այդ աշխատեք հին ադմինում․ տվյալները ընդհանուր են։',
|
||||
'soon.open': 'Բացել հին ադմինը',
|
||||
|
||||
'dash.morning': 'Բարի լույս, {name}',
|
||||
'dash.day': 'Բարի օր, {name}',
|
||||
'dash.evening': 'Բարի երեկո, {name}',
|
||||
'dash.attention': 'Պահանջում է ուշադրություն',
|
||||
'dash.allClear': 'Շտապ ոչինչ չկա․ ամեն ինչ արված է։',
|
||||
'att.unassigned': 'Պատվերներ առանց կատարողի',
|
||||
'att.unreadChats': 'Չկարդացված հաղորդագրություններ',
|
||||
'att.webRequests': 'Հայտեր կայքից',
|
||||
'att.leads': 'Տեղափոխման հայտեր',
|
||||
'att.appRequests': 'Հայտեր հավելվածից',
|
||||
'att.lowRatings': '3 և ցածր գնահատականներ այս շաբաթ',
|
||||
'att.expiring': 'Ավարտվում են 7 օրում',
|
||||
'dash.money': 'Գումար',
|
||||
'dash.today': 'Այսօր',
|
||||
'dash.week': '7 օր',
|
||||
'dash.month': '30 օր',
|
||||
'dash.vsYesterday': 'երեկ՝ {sum}',
|
||||
'dash.vsPrev': 'նախորդ {period}-ի համեմատ',
|
||||
'dash.orders': 'պատվերներ',
|
||||
'dash.subs': 'բաժանորդագրություններ',
|
||||
'dash.chart': 'Եկամուտն ըստ օրերի, 30 օր',
|
||||
'dash.subsTitle': 'Բաժանորդագրություններ',
|
||||
'dash.gifts': 'Որից նվեր՝ {n}',
|
||||
'dash.expiring': 'Ավարտվում են առաջիկա 7 օրում',
|
||||
'dash.noExpiring': 'Առաջիկա շաբաթ ոչինչ չի ավարտվում։',
|
||||
'dash.conversion': 'Նվեր → գնում',
|
||||
'dash.conversionHint': 'Նվեր ստացած {total} հաճախորդից {paid}-ը հետո գնել են',
|
||||
'dash.growth': 'Աճ',
|
||||
'dash.newClients': 'Նոր հաճախորդներ',
|
||||
'dash.prevMonth': 'նախորդ 30 օրում՝ {n}',
|
||||
'dash.sources': 'Որտեղից են եկել, 30 օր',
|
||||
'dash.funnel': 'Ձագար, 30 օր',
|
||||
'funnel.leads': 'Հայտեր կայքից',
|
||||
'funnel.clients': 'Հաճախորդներ հավելվածում',
|
||||
'funnel.paying': 'Վճարել են',
|
||||
'dash.team': 'Թիմ և գործընկերներ',
|
||||
'dash.topProviders': 'Լավագույն կատարողներ',
|
||||
'dash.bottomProviders': 'Արժե խոսել',
|
||||
'dash.topPartners': 'Հաստատություններ՝ քարտի սկանավորումներ, 30 օր',
|
||||
'dash.salesSoon': 'Վաճառողների արդյունքները կհայտնվեն CRM-ի հետ։',
|
||||
'dash.empty': 'Դեռ դատարկ է',
|
||||
'dash.feed': 'Լրահոս',
|
||||
'feed.subscription': '{plan} · {source}',
|
||||
'feed.payment': '№{number} պատվերը վճարված է',
|
||||
'dash.ratings': '{n} գնահատական',
|
||||
'dash.scans': '{n} սկան',
|
||||
|
||||
'source.organic': 'Ինքն են գտել',
|
||||
'source.deeplink': 'Գործակալության հղում',
|
||||
'source.manual_code': 'Գործակալության կոդ',
|
||||
'source.agency': '«{name}» գործակալություն',
|
||||
'source.utm': 'Գովազդ՝ {name}',
|
||||
|
||||
'plan.BAREV_PLUS': 'Barev+',
|
||||
'plan.ELITE': 'Elite',
|
||||
'plan.none': 'Առանց բաժանորդագրության',
|
||||
'subSource.PAID': 'Գնում',
|
||||
'subSource.MANUAL': 'Թիմի նվեր',
|
||||
'subSource.AGENCY_GIFT': 'Գործակալության նվեր',
|
||||
'subStatus.ACTIVE': 'Ակտիվ',
|
||||
'subStatus.EXPIRED': 'Ավարտված',
|
||||
'subStatus.CANCELLED': 'Չեղարկված',
|
||||
'pay.cash': 'Կանխիկ',
|
||||
'pay.transfer': 'Փոխանցում',
|
||||
'pay.card': 'Քարտ տերմինալով',
|
||||
'pay.link': 'Վճարման հղում',
|
||||
'segment.MOVING': 'Տեղափոխվում է',
|
||||
'segment.LIVING': 'Ապրում է Հայաստանում',
|
||||
|
||||
'clients.title': 'Հաճախորդներ',
|
||||
'clients.search': 'Անուն, @username, հեռախոս կամ քարտի համար',
|
||||
'clients.found': 'Գտնվել է՝ {n}',
|
||||
'clients.revenueTotal': 'բերել են {sum}',
|
||||
'col.client': 'Հաճախորդ',
|
||||
'col.sub': 'Բաժանորդագրություն',
|
||||
'col.source': 'Աղբյուր',
|
||||
'col.revenue': 'Բերել է',
|
||||
'col.since': 'Մեզ հետ է',
|
||||
'filter.plan': 'Բաժանորդագրություն',
|
||||
'filter.all': 'Բոլորը',
|
||||
'filter.any': 'Բաժանորդագրությամբ',
|
||||
'filter.none': 'Առանց բաժանորդագրության',
|
||||
'filter.subSource': 'Ինչպես է ստացել',
|
||||
'filter.source': 'Աղբյուր',
|
||||
'filter.agency': 'Գործակալություն',
|
||||
'filter.tag': 'Պիտակ',
|
||||
'filter.segment': 'Սցենար',
|
||||
'filter.paying': 'Վճարել են',
|
||||
'filter.payingYes': 'Վճարել են',
|
||||
'filter.payingNo': 'Երբեք չեն վճարել',
|
||||
'filter.expiring': 'Ավարտվում է 7 օրում',
|
||||
'filter.reset': 'Զրոյացնել ֆիլտրերը',
|
||||
'filter.more': 'Ֆիլտրեր',
|
||||
'filter.anyValue': 'Ցանկացած',
|
||||
'sort.label': 'Հերթականություն',
|
||||
'sort.new': 'Նախ նորերը',
|
||||
'sort.revenue': 'Ամենաշատը բերածները',
|
||||
'sort.ends': 'Շուտով ավարտվող',
|
||||
'segments.title': 'Իմ ընտրանիները',
|
||||
'segments.save': 'Պահպանել ընտրանին',
|
||||
'segments.name': 'Ընտրանու անունը',
|
||||
'segments.saved': 'Ընտրանին պահպանված է',
|
||||
'segments.remove': 'Ջնջել «{name}» ընտրանին',
|
||||
'clients.export': 'Ներբեռնել Excel-ի համար',
|
||||
'clients.exported': 'Ֆայլը ներբեռնված է',
|
||||
'clients.broadcast': 'Առաքում',
|
||||
'clients.broadcastSoon': 'Ըստ ընտրանու առաքումները կհայտնվեն 5-րդ փուլում։',
|
||||
'clients.empty': 'Այս ֆիլտրերին ոչ ոք չի համապատասխանում։',
|
||||
'clients.emptyAll': 'Հաճախորդներ դեռ չկան․ կհայտնվեն, երբ գրեն բոտին։',
|
||||
'clients.prev': 'Հետ',
|
||||
'clients.next': 'Առաջ',
|
||||
'clients.page': '{from}–{to}՝ {total}-ից',
|
||||
|
||||
'card.back': 'Բոլոր հաճախորդները',
|
||||
'card.since': 'Մեզ հետ է {date}-ից',
|
||||
'card.telegram': 'Բացել Telegram-ում',
|
||||
'card.cardNo': 'Քարտ {code}',
|
||||
'card.noSub': 'Ակտիվ բաժանորդագրություն չկա',
|
||||
'card.noSubHint': 'Նվիրեք կամ վաճառեք բաժանորդագրություն․ հաճախորդը միանգամից կստանա զեղչեր։',
|
||||
'card.until': 'մինչև {date}',
|
||||
'card.daysLeft': 'մնաց {n} օր',
|
||||
'card.history': 'Բոլոր բաժանորդագրությունները',
|
||||
'card.grantedBy': 'տրամադրել է {name}',
|
||||
'card.giftFrom': 'նվեր «{name}»-ից',
|
||||
'card.paidVia': '{method}',
|
||||
'card.refunded': 'վերադարձ {sum}',
|
||||
'card.cancelledAt': 'չեղարկված {date}',
|
||||
'card.upcoming': 'կսկսվի {date}',
|
||||
'card.money': 'Բերել է',
|
||||
'card.fromOrders': 'պատվերներ',
|
||||
'card.fromSubs': 'բաժանորդագրություններ',
|
||||
'card.orders': 'Պատվերներ',
|
||||
'card.noOrders': 'Պատվերներ դեռ չկան։',
|
||||
'card.newOrder': 'Ստեղծել պատվեր',
|
||||
'card.newOrderSoon': 'Պատվեր հաճախորդի անունից՝ 3-րդ փուլում, պատվերների բաժնի հետ։',
|
||||
'card.profile': 'Պրոֆիլ',
|
||||
'card.source': 'Որտեղից է եկել',
|
||||
'card.agency': 'Գործակալություն',
|
||||
'card.language': 'Լեզու',
|
||||
'card.phone': 'Հեռախոս',
|
||||
'card.segment': 'Սցենար',
|
||||
'card.consent': 'Տվյալների մշակման համաձայնություն',
|
||||
'card.tags': 'Պիտակներ',
|
||||
'card.addTag': 'Նոր պիտակ',
|
||||
'card.note': 'Թիմի նշում',
|
||||
'card.notePh': 'Հաճախորդը չի տեսնում այս նշումը',
|
||||
'card.saved': 'Պահպանված է',
|
||||
'card.activity': 'Հաճախորդի մատյան',
|
||||
'card.noActivity': 'Թիմը դեռ ոչինչ չի փոխել։',
|
||||
'card.stats': 'Ակտիվություն',
|
||||
'stat.cardUsages': 'քարտի սկան',
|
||||
'stat.leads': 'տեղափոխման հայտ',
|
||||
'stat.requests': 'հայտ հավելվածից',
|
||||
'stat.concierge': 'հաղորդագրություն չատում',
|
||||
|
||||
'act.gift': 'Նվիրել',
|
||||
'act.sell': 'Վաճառել',
|
||||
'act.extend': 'Փոխել ժամկետը',
|
||||
'act.cancel': 'Չեղարկել',
|
||||
'act.changePlan': 'Փոխել սակագինը',
|
||||
'act.refund': 'Վերադարձնել գումարը',
|
||||
|
||||
'audit.bo.clients.gift': 'նվիրել է բաժանորդագրություն',
|
||||
'audit.bo.clients.sell': 'վաճառել է բաժանորդագրություն',
|
||||
'audit.bo.clients.extend': 'փոխել է ժամկետը',
|
||||
'audit.bo.clients.cancel': 'չեղարկել է բաժանորդագրությունը',
|
||||
'audit.bo.clients.changePlan': 'փոխել է սակագինը',
|
||||
'audit.bo.clients.refund': 'ձևակերպել է վերադարձ',
|
||||
'audit.bo.clients.setTags': 'փոխել է պիտակները',
|
||||
'audit.bo.clients.setNote': 'փոխել է նշումը',
|
||||
|
||||
'dlg.giftTitle': 'Նվիրել բաժանորդագրություն',
|
||||
'dlg.giftHint': 'Այն կնշվի որպես ձեր նվերը։',
|
||||
'dlg.sellTitle': 'Վաճառել բաժանորդագրություն',
|
||||
'dlg.plan': 'Սակագին',
|
||||
'dlg.period': 'Ժամկետ',
|
||||
'dlg.period1': '1 ամիս',
|
||||
'dlg.period3': '3 ամիս',
|
||||
'dlg.period6': '6 ամիս',
|
||||
'dlg.period12': '1 տարի',
|
||||
'dlg.periodCustom': 'Այլ',
|
||||
'dlg.days': 'Օր',
|
||||
'dlg.amount': 'Գումար, ֏',
|
||||
'dlg.method': 'Ինչպես է վճարել',
|
||||
'dlg.note': 'Մեկնաբանություն թիմի համար',
|
||||
'dlg.linkSoon': 'Վճարման հղումը կմիացնենք վճարային համակարգը միացնելուց հետո։',
|
||||
'dlg.giftConfirm': 'Նվիրել',
|
||||
'dlg.sellConfirm': 'Նշել վճարումը',
|
||||
'dlg.extendTitle': 'Փոխել ժամկետը',
|
||||
'dlg.extendHint': 'Բացասական թիվը կրճատում է ժամկետը։',
|
||||
'dlg.newEnd': 'Նոր ավարտի ամսաթիվ՝ {date}',
|
||||
'dlg.extendConfirm': 'Պահպանել ժամկետը',
|
||||
'dlg.cancelTitle': 'Չեղարկել բաժանորդագրությունը',
|
||||
'dlg.cancelHint': 'Հաճախորդը միանգամից կկորցնի զեղչերն ու արտոնությունները։',
|
||||
'dlg.reason': 'Պատճառ',
|
||||
'dlg.cancelConfirm': 'Չեղարկել բաժանորդագրությունը',
|
||||
'dlg.planTitle': 'Փոխել սակագինը',
|
||||
'dlg.planConfirm': 'Փոխել սակագինը',
|
||||
'dlg.refundTitle': 'Վերադարձնել գումարը',
|
||||
'dlg.refundPaid': 'Վճարված է {sum}, արդեն վերադարձված՝ {back}',
|
||||
'dlg.refundCancel': 'Միանգամից չեղարկել բաժանորդագրությունը',
|
||||
'dlg.refundConfirm': 'Ձևակերպել վերադարձ',
|
||||
'done.gift': 'Բաժանորդագրությունը նվիրված է',
|
||||
'done.sell': 'Վճարումը նշված է',
|
||||
'done.extend': 'Ժամկետը փոխված է',
|
||||
'done.cancel': 'Բաժանորդագրությունը չեղարկված է',
|
||||
'done.changePlan': 'Սակագինը փոխված է',
|
||||
'done.refund': 'Վերադարձը ձևակերպված է',
|
||||
|
||||
'team.title': 'Թիմ',
|
||||
'team.lead': 'Ով է աշխատում ադմինում և ինչ հասանելիություն ունի։ Մեկ մարդը կարող է ունենալ մի քանի դեր։',
|
||||
'team.add': 'Ավելացնել մարդու',
|
||||
'team.name': 'Անուն',
|
||||
'team.roles': 'Դերեր',
|
||||
'team.create': 'Ավելացնել',
|
||||
'team.linkTitle': 'Հղում {name}-ի համար',
|
||||
'team.linkHint': 'Ուղարկեք նրան։ Կբացի աշխատանքային բոտը, Telegram-ը կկապվի, և կարող է մուտք գործել։',
|
||||
'team.tgLinked': 'Telegram-ը կապված է',
|
||||
'team.tgNotLinked': 'Telegram-ը կապված չէ',
|
||||
'team.newLink': 'Կապման հղում',
|
||||
'team.disable': 'Անջատել մուտքը',
|
||||
'team.enable': 'Վերադարձնել մուտքը',
|
||||
'team.disabled': 'Մուտքն անջատված է',
|
||||
'team.you': 'դուք',
|
||||
'team.saved': 'Դերերը պահպանված են',
|
||||
};
|
||||
|
||||
const DICTS: Record<BoLocale, Dict> = { ru, en, hy };
|
||||
|
||||
export type T = (key: BoKey | (string & {}), params?: Record<string, string | number | null | undefined>) => string;
|
||||
|
||||
function makeT(locale: BoLocale): T {
|
||||
const dict = DICTS[locale];
|
||||
return (key, params) => {
|
||||
let s: string = (dict as Record<string, string>)[key] ?? (ru as Record<string, string>)[key] ?? key;
|
||||
if (params) for (const [k, v] of Object.entries(params)) s = s.replaceAll(`{${k}}`, String(v ?? ''));
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
interface Ctx {
|
||||
locale: BoLocale;
|
||||
setLocale: (l: BoLocale) => void;
|
||||
t: T;
|
||||
/** общие переводы проекта: services.<code>.name и т.п. */
|
||||
shared: (key: string) => string;
|
||||
amd: (n: number) => string;
|
||||
date: (d: Date) => string;
|
||||
dateTime: (d: Date) => string;
|
||||
/** сокращённая сумма для плотных мест: 1,2 млн */
|
||||
amdShort: (n: number) => string;
|
||||
}
|
||||
|
||||
const I18nCtx = createContext<Ctx | null>(null);
|
||||
const KEY = 'barev.bo.locale';
|
||||
|
||||
function initialLocale(): BoLocale {
|
||||
try {
|
||||
const saved = localStorage.getItem(KEY);
|
||||
if (saved && (BO_LOCALES as readonly string[]).includes(saved)) return saved as BoLocale;
|
||||
} catch {
|
||||
/* приватный режим */
|
||||
}
|
||||
const nav = navigator.language.slice(0, 2);
|
||||
return (BO_LOCALES as readonly string[]).includes(nav) ? (nav as BoLocale) : 'ru';
|
||||
}
|
||||
|
||||
export function BoI18nProvider({ children }: { children: ReactNode }) {
|
||||
const [locale, setLocaleState] = useState<BoLocale>(initialLocale);
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = locale;
|
||||
}, [locale]);
|
||||
const value = useMemo<Ctx>(() => {
|
||||
const sharedT = createSharedT(locale as SharedLocale);
|
||||
const intl = locale === 'en' ? 'en-GB' : locale === 'hy' ? 'hy-AM' : 'ru-RU';
|
||||
const compact = new Intl.NumberFormat(intl, { notation: 'compact', maximumFractionDigits: 1 });
|
||||
return {
|
||||
locale,
|
||||
setLocale: (l) => {
|
||||
setLocaleState(l);
|
||||
try {
|
||||
localStorage.setItem(KEY, l);
|
||||
} catch {
|
||||
/* приватный режим */
|
||||
}
|
||||
},
|
||||
t: makeT(locale),
|
||||
shared: sharedT,
|
||||
amd: (n) => formatAmd(locale as SharedLocale, n),
|
||||
amdShort: (n) => `${compact.format(n)} ֏`,
|
||||
date: (d) => formatDateTime(locale as SharedLocale, d, { day: 'numeric', month: 'short', year: 'numeric' }),
|
||||
dateTime: (d) => formatDateTime(locale as SharedLocale, d, { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }),
|
||||
};
|
||||
}, [locale]);
|
||||
return <I18nCtx.Provider value={value}>{children}</I18nCtx.Provider>;
|
||||
}
|
||||
|
||||
export function useT() {
|
||||
const v = useContext(I18nCtx);
|
||||
if (!v) throw new Error('BoI18nProvider missing');
|
||||
return v;
|
||||
}
|
||||
|
||||
/** Текст ошибки API: код бизнес-ошибки → перевод, иначе сообщение */
|
||||
export function errText(t: T, err: unknown): string {
|
||||
const msg = (err as { message?: string })?.message ?? '';
|
||||
const known = `err.${msg}`;
|
||||
const tr = t(known);
|
||||
return tr !== known ? tr : t('ui.error', { msg: msg || '—' });
|
||||
}
|
||||
Reference in new issue
Block a user