clarify(locales): sweep em-dashes from copy across 4 locales

Audit P1: 40 em-dash hits in src/locales/. Impeccable rule:
'No em dashes. Use commas, colons, semicolons, periods, or
parentheses.' Sentence-level em-dashes obscure structure;
explicit punctuation parses faster.

30 em-dashes replaced across en/ru/zh/fa:
  • 'X — Y' joining a clause          → 'X, Y'
  • 'X — Y' introducing value/detail  → 'X: Y'  ('X:Y' in zh)
  • 'X — Y' sentence-ending fragment  → 'X. Y'  ('X。Y' in zh)

Affected keys (same in all 4 locales unless noted):
  telegramReopenHint (ru only)
  freeDesc
  searchResults
  openUrlDirectHint
  deleteSuccessWithSubscriptions
  promoGroupsHint
  activePaidWarning (ru only)
  errors.title
  activateSuccessDesc
  noBonusDescription (en, zh, fa — no ru key)
  campaignRegistrations (en, zh, fa)

10 em-dashes kept intentionally (not copy violations):
  • noTariff: '—'                    (4 locales) — IS the value
                                       placeholder, not text
  • previewEmpty: '— empty —'        (ru + en) — decorative
                                       brackets framing 'empty'
  • statusLegend: ' — X •  — Y …' (4 locales) — legend
                                       convention, icon→label
                                       separator; switching to ':'
                                       would need a wider rework
                                       of the visual rhythm

All 4 JSON files parse cleanly; TSC/lint/build green.
This commit is contained in:
c0mrade
2026-05-26 20:36:19 +03:00
parent eb13689ae7
commit 80898ee2fe
4 changed files with 30 additions and 30 deletions

View File

@@ -282,7 +282,7 @@
"trialOffer": {
"freeTitle": "Free Trial Available",
"paidTitle": "Trial Subscription",
"freeDesc": "Try our VPN for free no commitment required",
"freeDesc": "Try our VPN for free, no commitment required",
"paidDesc": "Get started with a trial subscription"
},
"stats": {
@@ -1475,7 +1475,7 @@
"description": "Search and manage payments",
"searchPlaceholder": "Search: invoice, TG ID, @username, email...",
"searchHint": "Examples: SP-78291, @username, 123456789, user@mail.ru",
"searchResults": "Results for: {{query}} — found {{count}}",
"searchResults": "Results for: {{query}}. Found {{count}}",
"resetSearch": "Reset",
"statusAll": "All",
"statusPending": "Pending",
@@ -1521,7 +1521,7 @@
"methodEnabled": "Method enabled",
"providerNotConfigured": "Provider not configured in env",
"openUrlDirect": "Open payment page directly",
"openUrlDirectHint": "Skip the link panel the provider opens inside the MiniApp/tab right after the click. After payment the user returns to /balance/top-up/result. Ignored for Stars/CryptoBot and other t.me/ links.",
"openUrlDirectHint": "Skip the link panel: the provider opens inside the MiniApp/tab right after the click. After payment the user returns to /balance/top-up/result. Ignored for Stars/CryptoBot and other t.me/ links.",
"displayName": "Display name",
"displayNameHint": "Leave empty for default name",
"subOptions": "Payment options",
@@ -2151,7 +2151,7 @@
"summaryErrors": "{{count}} errors"
},
"errors": {
"title": "{{count}} errors — show details"
"title": "{{count}} errors. Show details"
},
"filters": {
"search": "Search by ID, username, email",
@@ -2398,7 +2398,7 @@
"confirmDeleteText": "This action cannot be undone. The tariff will be permanently deleted.",
"confirmDeleteWithSubscriptions": "This tariff has {{count}} active subscriptions. After deletion, users will need to select a new tariff when renewing.",
"deleteSuccess": "Tariff deleted successfully",
"deleteSuccessWithSubscriptions": "Tariff deleted. {{count}} subscriptions reset — users will choose a new tariff on renewal.",
"deleteSuccessWithSubscriptions": "Tariff deleted. {{count}} subscriptions reset. Users will choose a new tariff on renewal.",
"saveOrder": "Save order",
"orderSaved": "Order saved",
"dragToReorder": "Drag to reorder",
@@ -2459,7 +2459,7 @@
"serversTabHint": "Select servers available on this tariff.",
"noServersAvailable": "No servers available",
"promoGroupsTitle": "Promo Groups",
"promoGroupsHint": "Select promo groups that have access to this tariff. If none selected available to everyone.",
"promoGroupsHint": "Select promo groups that have access to this tariff. If none selected, available to everyone.",
"noPromoGroups": "No promo groups",
"extraDeviceTitle": "Additional devices",
"devicePriceLabel": "Device price (30 days):",
@@ -2612,7 +2612,7 @@
"notSelected": "Not selected",
"tariffOption": "{{traffic}} GB, {{devices}} dev.",
"durationDays": "Duration (days)",
"noBonusDescription": "Campaign without bonuses only for tracking clicks and registrations.",
"noBonusDescription": "Campaign without bonuses: only for tracking clicks and registrations.",
"active": "Active",
"cancel": "Cancel",
"save": "Save",
@@ -2662,7 +2662,7 @@
},
"users": {
"title": "Campaign Users",
"campaignRegistrations": "{{name}} {{count}} registrations",
"campaignRegistrations": "{{name}}: {{count}} registrations",
"noUsers": "No registered users",
"user": "User",
"bonus": "Bonus",
@@ -4862,7 +4862,7 @@
"activateButton": "Activate gift",
"activating": "Activating...",
"activateSuccess": "Gift activated!",
"activateSuccessDesc": "{{tariff}} {{days}} days added to your subscription",
"activateSuccessDesc": "{{tariff}}: {{days}} days added to your subscription",
"activateError": "Failed to activate gift",
"activateSelfError": "You cannot activate your own gift",
"myGiftsEmpty": "No gifts yet",

View File

@@ -1511,7 +1511,7 @@
"description": "جستجو و مدیریت پرداخت‌ها",
"searchPlaceholder": "جستجو: فاکتور، شناسه تلگرام، @نام‌کاربری، ایمیل...",
"searchHint": "مثال‌ها: SP-78291, @username, 123456789, user@mail.ru",
"searchResults": "نتایج برای: {{query}} {{count}} یافت شد",
"searchResults": "نتایج برای: {{query}}. {{count}} یافت شد",
"resetSearch": "بازنشانی",
"statusAll": "همه",
"statusPending": "در انتظار",
@@ -1915,7 +1915,7 @@
"confirmDeleteText": "این عمل قابل بازگشت نیست. تعرفه برای همیشه حذف می‌شود.",
"confirmDeleteWithSubscriptions": "این تعرفه {{count}} اشتراک فعال دارد. پس از حذف، کاربران هنگام تمدید باید تعرفه جدید انتخاب کنند.",
"deleteSuccess": "تعرفه با موفقیت حذف شد",
"deleteSuccessWithSubscriptions": "تعرفه حذف شد. تعرفه {{count}} اشتراک بازنشانی شد کاربران هنگام تمدید تعرفه جدید انتخاب می‌کنند.",
"deleteSuccessWithSubscriptions": "تعرفه حذف شد. تعرفه {{count}} اشتراک بازنشانی شد. کاربران هنگام تمدید تعرفه جدید انتخاب می‌کنند.",
"saveOrder": "ذخیره ترتیب",
"orderSaved": "ترتیب ذخیره شد",
"dragToReorder": "برای تغییر ترتیب بکشید",
@@ -2088,7 +2088,7 @@
"methodEnabled": "روش فعال است",
"providerNotConfigured": "ارائه‌دهنده در env تنظیم نشده",
"openUrlDirect": "صفحه پرداخت را مستقیماً باز کن",
"openUrlDirectHint": "بدون پنل لینک ارائه‌دهنده داخل MiniApp/تب بلافاصله پس از کلیک باز می‌شود. پس از پرداخت کاربر به /balance/top-up/result بازمی‌گردد. برای Stars/CryptoBot و سایر لینک‌های t.me/ این پرچم نادیده گرفته می‌شود.",
"openUrlDirectHint": "بدون پنل لینک: ارائه‌دهنده داخل MiniApp/تب بلافاصله پس از کلیک باز می‌شود. پس از پرداخت کاربر به /balance/top-up/result بازمی‌گردد. برای Stars/CryptoBot و سایر لینک‌های t.me/ این پرچم نادیده گرفته می‌شود.",
"displayName": "نام نمایشی",
"displayNameHint": "برای نام پیش‌فرض خالی بگذارید",
"subOptions": "گزینه‌های پرداخت",
@@ -2163,7 +2163,7 @@
"notSelected": "انتخاب نشده",
"tariffOption": "{{traffic}} GB، {{devices}} دستگاه",
"durationDays": "مدت (روز)",
"noBonusDescription": "کمپین بدون پاداش فقط برای ردیابی کلیک‌ها و ثبت‌نام‌ها.",
"noBonusDescription": "کمپین بدون پاداش: فقط برای ردیابی کلیک‌ها و ثبت‌نام‌ها.",
"active": "فعال",
"cancel": "لغو",
"save": "ذخیره",
@@ -2211,7 +2211,7 @@
},
"users": {
"title": "کاربران کمپین",
"campaignRegistrations": "{{name}} {{count}} ثبت‌نام",
"campaignRegistrations": "{{name}}: {{count}} ثبت‌نام",
"noUsers": "کاربر ثبت‌نام شده‌ای وجود ندارد",
"user": "کاربر",
"bonus": "پاداش",
@@ -3633,7 +3633,7 @@
"summaryErrors": "{{count}} خطا"
},
"errors": {
"title": "{{count}} خطا نمایش جزئیات"
"title": "{{count}} خطا. نمایش جزئیات"
},
"filters": {
"search": "جستجو بر اساس شناسه، نام کاربری، ایمیل",

View File

@@ -172,7 +172,7 @@
"registerHint": "Для регистрации по email сначала авторизуйтесь через Telegram, затем привяжите email в настройках.",
"telegramRequired": "Требуется авторизация через Telegram",
"telegramRetryFailed": "Авторизация не удалась. Закройте приложение и откройте заново.",
"telegramReopenHint": "Если проблема повторяется закройте и откройте приложение заново",
"telegramReopenHint": "Если проблема повторяется, закройте и откройте приложение заново",
"telegramNotConfigured": "Telegram бот не настроен",
"telegramUnavailable": "Вход через Telegram временно недоступен",
"authenticating": "Авторизация...",
@@ -295,7 +295,7 @@
"trialOffer": {
"freeTitle": "Бесплатный пробный период",
"paidTitle": "Пробная подписка",
"freeDesc": "Попробуйте наш VPN бесплатно без обязательств",
"freeDesc": "Попробуйте наш VPN бесплатно: без обязательств",
"paidDesc": "Начните с пробной подписки"
},
"stats": {
@@ -1497,7 +1497,7 @@
"description": "Поиск и управление платежами",
"searchPlaceholder": "Поиск: инвойс, TG ID, @username, email...",
"searchHint": "Примеры: SP-78291, @username, 123456789, user@mail.ru",
"searchResults": "Результаты по запросу: {{query}} — найдено {{count}}",
"searchResults": "Результаты по запросу: {{query}}. Найдено {{count}}",
"resetSearch": "Сбросить",
"statusAll": "Все",
"statusPending": "В ожидании",
@@ -1543,7 +1543,7 @@
"methodEnabled": "Метод включён",
"providerNotConfigured": "Провайдер не настроен в env",
"openUrlDirect": "Открывать страницу оплаты сразу",
"openUrlDirectHint": "Без панели со ссылкой провайдер открывается внутри MiniApp/вкладки сразу после клика. После оплаты юзер возвращается на /balance/top-up/result. Для Stars/CryptoBot и других t.me/-ссылок флаг игнорируется.",
"openUrlDirectHint": "Без панели со ссылкой: провайдер открывается внутри MiniApp/вкладки сразу после клика. После оплаты юзер возвращается на /balance/top-up/result. Для Stars/CryptoBot и других t.me/-ссылок флаг игнорируется.",
"displayName": "Отображаемое имя",
"displayNameHint": "Оставьте пустым для имени по умолчанию",
"subOptions": "Варианты оплаты",
@@ -2796,7 +2796,7 @@
"confirmDeleteText": "Это действие нельзя отменить. Тариф будет удалён навсегда.",
"confirmDeleteWithSubscriptions": "У этого тарифа {{count}} активных подписок. При удалении пользователям потребуется выбрать новый тариф при продлении.",
"deleteSuccess": "Тариф успешно удален",
"deleteSuccessWithSubscriptions": "Тариф удален. У {{count}} подписок сброшен тариф — при продлении пользователи выберут новый.",
"deleteSuccessWithSubscriptions": "Тариф удален. У {{count}} подписок сброшен тариф. При продлении пользователи выберут новый.",
"saveOrder": "Сохранить порядок",
"orderSaved": "Порядок сохранён",
"dragToReorder": "Перетащите для изменения порядка",
@@ -2857,7 +2857,7 @@
"serversTabHint": "Выберите серверы, доступные на этом тарифе.",
"noServersAvailable": "Нет доступных серверов",
"promoGroupsTitle": "Промо-группы",
"promoGroupsHint": "Выберите промо-группы, которым доступен этот тариф. Если не выбрано ничего доступен всем.",
"promoGroupsHint": "Выберите промо-группы, которым доступен этот тариф. Если не выбрано ничего, доступен всем.",
"noPromoGroups": "Нет промо-групп",
"extraDeviceTitle": "Докупка устройств",
"devicePriceLabel": "Цена за устройство (30 дней):",
@@ -3880,7 +3880,7 @@
"deleteSubscription": {
"warning": "Выбранные подписки будут безвозвратно удалены из бота и панели RemnaWave!",
"hint": "Пользователи потеряют доступ к VPN по удалённым подпискам. Это действие нельзя отменить.",
"activePaidWarning": "{{count}} из {{total}} выбранных подписок активные платные",
"activePaidWarning": "{{count}} из {{total}} выбранных подписок: активные платные",
"forceDeleteConfirm": "Подтверждаю удаление активных платных подписок"
},
"deleteUser": {
@@ -3917,7 +3917,7 @@
"summaryErrors": "{{count}} ошибок"
},
"errors": {
"title": "{{count}} ошибок — показать детали"
"title": "{{count}} ошибок. Показать детали"
},
"filters": {
"search": "Поиск по ID, нику, email",
@@ -5418,7 +5418,7 @@
"activateButton": "Активировать подарок",
"activating": "Активация...",
"activateSuccess": "Подарок активирован!",
"activateSuccessDesc": "{{tariff}} {{days}} дн. добавлено к вашей подписке",
"activateSuccessDesc": "{{tariff}}: {{days}} дн. добавлено к вашей подписке",
"activateError": "Не удалось активировать подарок",
"activateSelfError": "Нельзя активировать свой собственный подарок",
"myGiftsEmpty": "У вас пока нет подарков",

View File

@@ -1286,7 +1286,7 @@
"methodEnabled": "方法已启用",
"providerNotConfigured": "提供商未在env中配置",
"openUrlDirect": "直接打开支付页面",
"openUrlDirectHint": "跳过链接面板点击后提供商在 MiniApp/标签页内直接打开。支付完成后用户返回 /balance/top-up/result。Stars/CryptoBot 和其他 t.me/ 链接忽略此标志。",
"openUrlDirectHint": "跳过链接面板:点击后提供商在 MiniApp/标签页内直接打开。支付完成后用户返回 /balance/top-up/result。Stars/CryptoBot 和其他 t.me/ 链接忽略此标志。",
"displayName": "显示名称",
"displayNameHint": "留空以使用默认名称",
"subOptions": "支付选项",
@@ -1338,7 +1338,7 @@
"description": "搜索和管理支付",
"searchPlaceholder": "搜索发票、TG ID、@用户名、邮箱...",
"searchHint": "示例SP-78291、@username、123456789、user@mail.ru",
"searchResults": "搜索结果{{query}}找到 {{count}} 条",
"searchResults": "搜索结果:{{query}}找到 {{count}} 条",
"resetSearch": "重置",
"statusAll": "全部",
"statusPending": "待处理",
@@ -1955,7 +1955,7 @@
"confirmDeleteText": "此操作不可撤销。套餐将被永久删除。",
"confirmDeleteWithSubscriptions": "此套餐有 {{count}} 个活跃订阅。删除后,用户续订时需要选择新套餐。",
"deleteSuccess": "套餐删除成功",
"deleteSuccessWithSubscriptions": "套餐已删除。{{count}} 个订阅已重置用户续订时将选择新套餐。",
"deleteSuccessWithSubscriptions": "套餐已删除。{{count}} 个订阅已重置用户续订时将选择新套餐。",
"saveOrder": "保存排序",
"orderSaved": "排序已保存",
"dragToReorder": "拖动以重新排序",
@@ -2162,7 +2162,7 @@
"notSelected": "未选择",
"tariffOption": "{{traffic}} GB{{devices}} 台设备",
"durationDays": "时长(天)",
"noBonusDescription": "无奖励活动——仅用于跟踪点击和注册。",
"noBonusDescription": "无奖励活动:仅用于跟踪点击和注册。",
"active": "启用",
"cancel": "取消",
"save": "保存",
@@ -2210,7 +2210,7 @@
},
"users": {
"title": "活动用户",
"campaignRegistrations": "{{name}}{{count}} 次注册",
"campaignRegistrations": "{{name}}:{{count}} 次注册",
"noUsers": "没有注册用户",
"user": "用户",
"bonus": "奖励",
@@ -3632,7 +3632,7 @@
"summaryErrors": "{{count}} 错误"
},
"errors": {
"title": "{{count}} 个错误显示详情"
"title": "{{count}} 个错误显示详情"
},
"filters": {
"search": "按ID、用户名、邮箱搜索",