Merge remote-tracking branch 'origin/main' into feat/pay-v2-integration
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 25s
ci-pangolin / Lint — shellcheck (push) Successful in 29s
ci-pangolin / Cleartext Scan — Android 禁明文 (push) Successful in 22s
ci-pangolin / OpenAPI Sync Check (push) Successful in 40s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 19s
ci-pangolin / Flutter — analyze + test (push) Failing after 4m59s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 1m51s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Failing after 1m33s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Failing after 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m59s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (push) Failing after 4s

# Conflicts:
#	docs/index.html
#	server/cmd/server/main.go
This commit is contained in:
wangjia
2026-07-11 16:44:05 +08:00
228 changed files with 13418 additions and 1106 deletions
+337 -1
View File
@@ -11,7 +11,322 @@
// 接口形态与 .arb 一致,后续可平滑迁移到官方 l10n 工具链。
/// 受支持语言。单显——任一时刻只渲染其一。
enum AppLang { zh, en }
/// zh 中文 · en English · ja 日本語 · ko 한국어 · ru Русский · es Español。
enum AppLang { zh, en, ja, ko, ru, es }
/// 语言的本地名(用于语言切换 UI,各语言用自身文字标注)。
extension AppLangLabel on AppLang {
String get nativeLabel {
switch (this) {
case AppLang.zh:
return '中文';
case AppLang.en:
return 'English';
case AppLang.ja:
return '日本語';
case AppLang.ko:
return '한국어';
case AppLang.ru:
return 'Русский';
case AppLang.es:
return 'Español';
}
}
}
/// 少量「拿不到 AppText 实例的场景」(model / 非 Consumer 的 tile,只有 AppLang)
/// 用到的零散文案。exhaustive switch 保证 6 语全覆盖,不会漏译回退英文。
/// 仍归属 l10n 层(scan-redline 覆盖本文件),不违反「文案集中」铁律。
extension AppLangMisc on AppLang {
/// 节点不可用(agent 离线)。
String get unavailable {
switch (this) {
case AppLang.zh:
return '不可用';
case AppLang.en:
return 'Unavailable';
case AppLang.ja:
return '利用不可';
case AppLang.ko:
return '사용 불가';
case AppLang.ru:
return 'Недоступно';
case AppLang.es:
return 'No disponible';
}
}
/// 节点标签:流媒体优化。
String get tagStreaming {
switch (this) {
case AppLang.zh:
return '流媒体优化';
case AppLang.en:
return 'Streaming';
case AppLang.ja:
return 'ストリーミング最適化';
case AppLang.ko:
return '스트리밍 최적화';
case AppLang.ru:
return 'Оптимизация стриминга';
case AppLang.es:
return 'Optimización de streaming';
}
}
/// 节点尚未就绪(列表加载中/为空)。
String get nodesNotReady {
switch (this) {
case AppLang.zh:
return '节点尚未就绪,请稍候重试';
case AppLang.en:
return 'Nodes not ready, please retry';
case AppLang.ja:
return 'ノードの準備ができていません。しばらくして再試行してください';
case AppLang.ko:
return '노드가 아직 준비되지 않았습니다. 잠시 후 다시 시도하세요';
case AppLang.ru:
return 'Узлы не готовы, повторите попытку';
case AppLang.es:
return 'Nodos no listos, inténtalo de nuevo';
}
}
/// 连接失败(通用兜底)。
String get connectFailed {
switch (this) {
case AppLang.zh:
return '连接失败,请重试';
case AppLang.en:
return 'Connection failed, please retry';
case AppLang.ja:
return '接続に失敗しました。再試行してください';
case AppLang.ko:
return '연결에 실패했습니다. 다시 시도하세요';
case AppLang.ru:
return 'Не удалось подключиться, повторите попытку';
case AppLang.es:
return 'Error de conexión, inténtalo de nuevo';
}
}
/// 数据加载失败。
String get loadFailedRetry {
switch (this) {
case AppLang.zh:
return '加载失败,请重试';
case AppLang.en:
return 'Failed to load, retry';
case AppLang.ja:
return '読み込みに失敗しました。再試行してください';
case AppLang.ko:
return '불러오기에 실패했습니다. 다시 시도하세요';
case AppLang.ru:
return 'Не удалось загрузить, повторите';
case AppLang.es:
return 'Error al cargar, reintentar';
}
}
/// 无已登录设备。
String get noDevices {
switch (this) {
case AppLang.zh:
return '暂无已登录设备';
case AppLang.en:
return 'No devices yet';
case AppLang.ja:
return 'ログイン済みのデバイスはありません';
case AppLang.ko:
return '로그인된 기기가 없습니다';
case AppLang.ru:
return 'Пока нет устройств';
case AppLang.es:
return 'Aún no hay dispositivos';
}
}
/// 「改邮箱」按钮短标签。
String get changeEmail {
switch (this) {
case AppLang.zh:
return '改邮箱';
case AppLang.en:
return 'Change';
case AppLang.ja:
return '変更';
case AppLang.ko:
return '변경';
case AppLang.ru:
return 'Изменить';
case AppLang.es:
return 'Cambiar';
}
}
/// 相对时间(设备最后在线):刚刚 / X 分钟前 / X 小时前 / X 天前。
String relativeTime(Duration d) {
final m = d.inMinutes, h = d.inHours, days = d.inDays;
switch (this) {
case AppLang.zh:
if (m < 1) return '刚刚';
if (m < 60) return '$m 分钟前';
if (h < 24) return '$h 小时前';
return '$days 天前';
case AppLang.en:
if (m < 1) return 'just now';
if (m < 60) return '$m min ago';
if (h < 24) return '${h}h ago';
return '${days}d ago';
case AppLang.ja:
if (m < 1) return 'たった今';
if (m < 60) return '$m 分前';
if (h < 24) return '$h 時間前';
return '$days 日前';
case AppLang.ko:
if (m < 1) return '방금';
if (m < 60) return '$m분';
if (h < 24) return '$h시간';
return '$days일';
case AppLang.ru:
if (m < 1) return 'только что';
if (m < 60) return '$m мин назад';
if (h < 24) return '$h ч назад';
return '$days дн назад';
case AppLang.es:
if (m < 1) return 'ahora mismo';
if (m < 60) return 'hace $m min';
if (h < 24) return 'hace $h h';
return 'hace $days d';
}
}
/// 更新下载进度:「正在下载 X%」。
String updateDownloadingPercent(int pct) {
switch (this) {
case AppLang.zh:
return '正在下载 $pct%';
case AppLang.en:
return 'Downloading $pct%';
case AppLang.ja:
return 'ダウンロード中 $pct%';
case AppLang.ko:
return '다운로드 중 $pct%';
case AppLang.ru:
return 'Загрузка $pct%';
case AppLang.es:
return 'Descargando $pct%';
}
}
/// 下载完成、正在安装。
String get updateInstalling {
switch (this) {
case AppLang.zh:
return '下载完成,正在安装…';
case AppLang.en:
return 'Downloaded, installing…';
case AppLang.ja:
return 'ダウンロード完了、インストール中…';
case AppLang.ko:
return '다운로드 완료, 설치 중…';
case AppLang.ru:
return 'Загружено, установка…';
case AppLang.es:
return 'Descargado, instalando…';
}
}
/// 下载失败。
String get updateDownloadFailed {
switch (this) {
case AppLang.zh:
return '下载失败,请重试或改用浏览器下载。';
case AppLang.en:
return 'Download failed. Retry or download in your browser.';
case AppLang.ja:
return 'ダウンロードに失敗しました。再試行するかブラウザでダウンロードしてください。';
case AppLang.ko:
return '다운로드에 실패했습니다. 다시 시도하거나 브라우저로 다운로드하세요.';
case AppLang.ru:
return 'Не удалось загрузить. Повторите или скачайте в браузере.';
case AppLang.es:
return 'Error de descarga. Reintenta o descarga en el navegador.';
}
}
/// macOS:已下载,提示手动拖入应用程序。
String get updateMacReveal {
switch (this) {
case AppLang.zh:
return '已下载并解压。请在访达中将 Pangolin 拖入「应用程序」文件夹替换旧版,然后重新打开。';
case AppLang.en:
return 'Downloaded and extracted. In Finder, drag Pangolin into your Applications folder to replace the old version, then reopen.';
case AppLang.ja:
return 'ダウンロードと展開が完了しました。Finder で Pangolin を「アプリケーション」フォルダにドラッグして置き換え、再度開いてください。';
case AppLang.ko:
return '다운로드 및 압축 해제 완료. Finder에서 Pangolin을 「응용 프로그램」 폴더로 드래그해 이전 버전을 교체한 뒤 다시 여세요.';
case AppLang.ru:
return 'Загружено и распаковано. В Finder перетащите Pangolin в папку «Программы», заменив старую версию, затем откройте заново.';
case AppLang.es:
return 'Descargado y extraído. En Finder, arrastra Pangolin a tu carpeta de Aplicaciones para reemplazar la versión anterior y vuelve a abrir.';
}
}
/// 失败对话框:改用浏览器下载。
String get updateOpenBrowser {
switch (this) {
case AppLang.zh:
return '浏览器下载';
case AppLang.en:
return 'Open in browser';
case AppLang.ja:
return 'ブラウザで開く';
case AppLang.ko:
return '브라우저에서 열기';
case AppLang.ru:
return 'Открыть в браузере';
case AppLang.es:
return 'Abrir en el navegador';
}
}
/// 通用「取消」。
String get updateCancelBtn {
switch (this) {
case AppLang.zh:
return '取消';
case AppLang.en:
return 'Cancel';
case AppLang.ja:
return 'キャンセル';
case AppLang.ko:
return '취소';
case AppLang.ru:
return 'Отмена';
case AppLang.es:
return 'Cancelar';
}
}
/// 更新 banner:「立即更新」。
String get updateNow {
switch (this) {
case AppLang.zh:
return '立即更新';
case AppLang.en:
return 'Update now';
case AppLang.ja:
return '今すぐ更新';
case AppLang.ko:
return '지금 업데이트';
case AppLang.ru:
return 'Обновить';
case AppLang.es:
return 'Actualizar';
}
}
}
/// 全部界面文案的抽象契约。zh / en 各实现一份。
abstract class AppText {
@@ -45,8 +360,20 @@ abstract class AppText {
String get quotaToday;
String get minutes;
String get quotaFree;
String get planFreeTag; // 额度卡右上短标签「免费版」(不含「每日10分钟」,避免挤占行宽)
String get watchAd;
String get adUnlocked;
// 免费版 10 分钟卡控 + 看广告加时(累加式)
String get quotaLeftLabel; // 连接期倒计时前缀「剩余」
String get quotaUsedUp; // 额度卡:今日已用完
String get watchAdMore; // 「看广告加时」CTA
String get quotaExhaustedNotice; // 倒计时归零自动切断提示
String get adPlaying; // 占位广告:播放中
String adRewarded(int minutes); // 占位广告:已加 N 分钟
String get adFailed; // 看广告加时失败
String get quotaDesktopTitle; // 桌面版额度耗尽弹窗标题
String get quotaDesktopBody; // 桌面版额度耗尽弹窗正文
String get gotIt; // 知道了
// ── 节点页 ──
String get chooseNode;
@@ -59,6 +386,7 @@ abstract class AppText {
// 连通看门狗:当前节点数据面不可用时的提示
String get nodeUnhealthySwitched; // 智能选择已自动切换
String get nodeUnhealthyError; // 手动节点:断开并提示
String get nodeReconnecting; // 弱网抖动:自动重连当前节点中(#18)
String get smartSub;
String get recommended;
@@ -135,6 +463,14 @@ abstract class AppText {
String get killSwitch;
String get killSwitchSub;
String get checkUpdate;
String get webUserCenter; // 「用户中心(网页)」入口(App→Web SSO 免登)
// 更新检查(设置页「检查更新」手动触发)
String updateAvailableTitle(String version); // 「发现新版本 vX.Y.Z」
String get updateNotesFallback; // 无 release_notes 时的兜底文案
String get updateLater;
String get updateDownload;
String get updateUpToDate; // 检查后:已是最新版本
String get updateCheckFailed; // 检查失败(网络异常)
// ── 套餐选择 ──
String get choosePlan;
+38
View File
@@ -53,9 +53,31 @@ class StringsEn extends AppText {
@override
String get quotaFree => 'Free · 10 min/day';
@override
String get planFreeTag => 'Free';
@override
String get watchAd => 'Watch ad to start';
@override
String get adUnlocked => 'Unlocked for today';
@override
String get quotaLeftLabel => 'Left';
@override
String get quotaUsedUp => 'Daily free time used up';
@override
String get watchAdMore => 'Watch ad for more time';
@override
String get quotaExhaustedNotice => 'Daily free time used up. Watch an ad to add time or upgrade.';
@override
String get adPlaying => 'Ad playing…';
@override
String adRewarded(int minutes) => '+$minutes min added';
@override
String get adFailed => 'Failed to add time, please retry';
@override
String get quotaDesktopTitle => 'Daily free time used up';
@override
String get quotaDesktopBody => 'Watch ads in the mobile app to add time, or upgrade for unlimited access.';
@override
String get gotIt => 'Got it';
@override
String get chooseNode => 'Choose server';
@@ -74,6 +96,8 @@ class StringsEn extends AppText {
@override
String get nodeUnhealthyError => 'Current node is unreachable — reconnect or switch node';
@override
String get nodeReconnecting => 'Network unstable, reconnecting…';
@override
String get smartSub => 'Picks the best node for your network';
@override
String get recommended => 'Recommended';
@@ -211,6 +235,20 @@ class StringsEn extends AppText {
String get killSwitchSub => 'Block traffic if the link drops';
@override
String get checkUpdate => 'Check for updates';
@override
String get webUserCenter => 'User Center (Web)';
@override
String updateAvailableTitle(String version) => 'New version v$version available';
@override
String get updateNotesFallback => 'This update includes fixes and stability improvements.';
@override
String get updateLater => 'Later';
@override
String get updateDownload => 'Download update';
@override
String get updateUpToDate => 'You are up to date';
@override
String get updateCheckFailed => 'Update check failed, try again later';
@override
String get choosePlan => 'Choose plan';
+373
View File
@@ -0,0 +1,373 @@
// strings_es.dart — Recursos de texto en español (visualización monolingüe)
//
// Palabras prohibidas: VPN / traspasar muros / red científica / romper el bloqueo / cruce libre / Go anywhere.
// Posicionamiento: aceleración de red / optimización de la experiencia + privacidad.
// La numeración de planes sigue design/CLAUDE.md §7.
import 'app_text.dart';
class StringsEs extends AppText {
const StringsEs();
@override
AppLang get lang => AppLang.es;
@override
String get brand => 'Pangolin';
@override
String get online => '● En línea';
@override
String get offline => '○ Desconectado';
@override
String get capOff => 'Toca para conectar';
@override
String get capConnecting => 'Conectando…';
@override
String get capOn => 'Conectado · Cifrado';
@override
String get connectNow => 'CONECTAR';
@override
String get secure => 'SEGURO';
@override
String get tabConnect => 'Conectar';
@override
String get tabServers => 'Servidores';
@override
String get tabStats => 'Estadísticas';
@override
String get tabMe => 'Cuenta';
@override
String get currentNode => 'Nodo actual';
@override
String get download => 'Bajada';
@override
String get upload => 'Subida';
@override
String get latency => 'Ping';
@override
String get quotaToday => 'Restante hoy';
@override
String get minutes => 'min';
@override
String get quotaFree => 'Gratis · 10 min/día';
@override
String get planFreeTag => 'Gratis';
@override
String get watchAd => 'Mira un anuncio para empezar';
@override
String get adUnlocked => 'Desbloqueado por hoy';
@override
String get quotaLeftLabel => 'Restante';
@override
String get quotaUsedUp => 'Tiempo gratis diario agotado';
@override
String get watchAdMore => 'Mira un anuncio para más tiempo';
@override
String get quotaExhaustedNotice => 'Tiempo gratis diario agotado. Mira un anuncio para añadir tiempo o mejora tu plan.';
@override
String get adPlaying => 'Reproduciendo anuncio…';
@override
String adRewarded(int minutes) => '+$minutes min añadidos';
@override
String get adFailed => 'No se pudo añadir tiempo, inténtalo de nuevo';
@override
String get quotaDesktopTitle => 'Tiempo gratis diario agotado';
@override
String get quotaDesktopBody => 'Mira anuncios en la app móvil para añadir tiempo, o mejora tu plan para acceso ilimitado.';
@override
String get gotIt => 'Entendido';
@override
String get chooseNode => 'Elegir servidor';
@override
String get searchPh => 'Buscar país / ciudad';
@override
String get smartSelect => 'Selección inteligente';
@override
String nodeSwitchTitle(String name) => '¿Cambiar a $name?';
@override
String get nodeSwitchBody => 'Esto desconecta el enlace actual y reconecta a este nodo.';
@override
String get nodeSwitchConfirm => 'Cambiar';
@override
String get nodeUnhealthySwitched => 'Nodo actual inaccesible: cambiado automáticamente';
@override
String get nodeUnhealthyError => 'El nodo actual está inaccesible: reconecta o cambia de nodo';
@override
String get nodeReconnecting => 'Red inestable, reconectando…';
@override
String get smartSub => 'Elige el mejor nodo para tu red';
@override
String get recommended => 'Recomendado';
@override
String get statsTitle => 'Estadísticas';
@override
String get trafficMonth => 'Tráfico';
@override
String get avgPing => 'Ping medio';
@override
String get durMonth => 'Tiempo';
@override
String get weekTraffic => 'Esta semana (GB)';
@override
List<String> get days7 => const ['L', 'M', 'X', 'J', 'V', 'S', 'D'];
@override
String get byDevice => 'Por dispositivo';
@override
String get noDeviceUsage => 'Aún no hay uso por dispositivo';
@override
String get periodMonth => 'Este mes';
@override
String get periodWeek => 'Esta semana';
@override
String get periodToday => 'Hoy';
@override
String get statDown => 'Descarga';
@override
String get statUp => 'Subida';
@override
String get statDuration => 'Duración';
@override
String get chartTwoWeeks => 'Últimas 2 semanas';
@override
String get allDevices => 'Todos los dispositivos';
@override
String get meTitle => 'Cuenta';
@override
String get proMember => 'Miembro PRO';
@override
String get freePlanName => 'Gratis';
@override
String get expires => 'Vence';
@override
String get upgradeBtn => 'Renovar / Mejorar';
@override
String get accInfoTitle => 'Información de la cuenta';
@override
String get accEmail => 'Correo';
@override
String get accPassword => 'Contraseña';
@override
String get accChange => 'Cambiar';
@override
String get deviceLimitTitle => 'Límite de dispositivos alcanzado';
@override
String get deviceLimitDesc => 'Tu plan permite hasta %s dispositivos. Elimina uno para continuar.';
@override
String get deviceLimitRemoveOldest => 'Eliminar el menos usado recientemente';
@override
String get myDevices => 'Mis dispositivos';
@override
String get devicesSub => 'Hasta 3 dispositivos en PRO';
@override
String get thisDevice => 'Este dispositivo';
@override
String get remove => 'Eliminar';
@override
String get devOnline => 'En línea';
@override
String get devOffline => 'Desconectado';
@override
String get devLastOnline => 'Última conexión';
@override
String get devNeverLogin => 'Nunca ha iniciado sesión';
@override
String get devForceLogout => 'Forzar cierre de sesión';
@override
String get devClearLogin => 'Borrar datos de sesión';
@override
String get devRename => 'Renombrar';
@override
String get devRenameHint => 'Nombre del dispositivo';
@override
String get devSave => 'Guardar';
@override
String get devForceLogoutConfirm => 'Esto revoca la sesión del dispositivo: será desconectado y deberá iniciar sesión de nuevo. El dispositivo permanece en la lista.';
@override
String get devClearLoginConfirm => 'Esto elimina el dispositivo de la lista por completo y revoca su sesión y su credencial del plano de datos. El dispositivo deberá iniciar sesión de nuevo para poder usarse.';
@override
String get devCancel => 'Cancelar';
@override
String get sessionRevokedTitle => 'Se cerró la sesión de este dispositivo';
@override
String get sessionRevokedBody => 'Tu cuenta cerró la sesión de este dispositivo desde otro dispositivo. Inicia sesión de nuevo.';
@override
String get sessionRevokedOk => 'Iniciar sesión de nuevo';
@override
String get redeemEntry => 'Canjear y comprar';
@override
String get contactEntry => 'Contáctanos';
@override
String get signOut => 'Cerrar sesión';
@override
String get language => 'Idioma';
@override
String get darkAppearance => 'Apariencia oscura';
@override
String get stateOn => 'Activado';
@override
String get followLight => 'Desactivado';
@override
String get protocol => 'Protocolo';
@override
String get settingsTitle => 'Ajustes';
@override
String get autostart => 'Iniciar al arrancar';
@override
String get autostartSub => 'Ejecutar automáticamente al iniciar el sistema';
@override
String get autoConnect => 'Conexión automática';
@override
String get autoConnectSub => 'Conectar automáticamente al abrir';
@override
String autoConnectingTo(String name) => 'Conectando automáticamente a $name';
@override
String get smartRoute => 'Enrutamiento inteligente';
@override
String get smartRouteSub => 'Acelera en el extranjero, directo en casa';
@override
String get killSwitch => 'Kill Switch';
@override
String get killSwitchSub => 'Bloquea el tráfico si se cae el enlace';
@override
String get checkUpdate => 'Buscar actualizaciones';
@override
String get webUserCenter => 'Centro de usuario (Web)';
@override
String updateAvailableTitle(String version) => 'Nueva versión v$version disponible';
@override
String get updateNotesFallback => 'Esta actualización incluye correcciones y mejoras de estabilidad.';
@override
String get updateLater => 'Más tarde';
@override
String get updateDownload => 'Descargar actualización';
@override
String get updateUpToDate => 'Ya tienes la última versión';
@override
String get updateCheckFailed => 'Falló la búsqueda de actualizaciones, inténtalo más tarde';
@override
String get choosePlan => 'Elegir plan';
@override
String get freePlan => 'Gratis';
@override
String get proPlan => 'Pro';
@override
String get teamPlan => 'Equipo';
@override
String get perMonth => '/mes';
@override
String get current => 'Actual';
@override
String get upgrade => 'Mejorar';
@override
String get choose => 'Elegir';
@override
String get mostPopular => 'Popular';
@override
List<String> get featsFree => const [
'Solo 1 nodo básico',
'10 min al día',
'Mira un anuncio para empezar',
'Prueba gratis de 7 días al registrarte',
];
@override
List<String> get featsPro =>
const ['Más de 80 ubicaciones', 'Ilimitado · rápido', '5 dispositivos', 'Optimizado para streaming'];
@override
List<String> get featsTeam =>
const ['Todo lo de Pro', '10 plazas', 'Facturación centralizada', 'Soporte prioritario'];
@override
String get redeemTitle => 'Canjear y comprar';
@override
String get redeemCodeTitle => 'Canjear un código';
@override
String get redeemPh => 'Introduce el código de activación';
@override
String get redeemBtn => 'Canjear';
@override
String get redeemOk => 'Activado · PRO desbloqueado';
@override
String get buyTitle => 'Dónde comprar';
@override
String get buySub => 'El pago dentro de la app no está disponible. Consigue un código mediante:';
@override
String get chStore => 'Tienda autoservicio';
@override
String get chEmail => 'Soporte por correo';
@override
String get contactTitle => 'Contáctanos';
@override
String get contactIntro =>
'¿Necesitas ayuda? Escríbenos por cualquiera de los canales de abajo: solemos responder en minutos.';
@override
String get contactEmail => 'Soporte por correo';
@override
String get contactStore => 'Tienda autoservicio';
@override
String get contactHoursTitle => 'Horario de soporte';
@override
String get contactHours => 'Todos los días 9:00 24:00 (GMT+8)';
@override
String get authTagline => 'Rápido · Estable · Sin complicaciones';
@override
String get tabLogin => 'Iniciar sesión';
@override
String get tabRegister => 'Registrarse';
@override
String get emailLabel => 'Correo';
@override
String get emailPh => 'tu@correo.com';
@override
String get pwLabel => 'Contraseña';
@override
String get pwPh => 'Introduce la contraseña';
@override
String get setPwPh => 'Crea una contraseña (para acceso multidispositivo)';
@override
String get codeLabel => 'Código de verificación';
@override
String codeSentTo(String email) => 'Código enviado a $email';
@override
String get sendCode => 'Enviar código';
@override
String get resend => 'Reenviar';
@override
String get doLogin => 'Iniciar sesión';
@override
String get doNext => 'Siguiente';
@override
String get doCreate => 'Crear cuenta';
@override
String get forgotPw => '¿Olvidaste la contraseña?';
@override
String get tos => 'Al continuar aceptas nuestros Términos y Política de privacidad';
@override
String get obSkip => 'Omitir';
@override
String get obNext => 'Siguiente';
@override
String get obAllow => 'Permitir y continuar';
@override
String get obStart => 'Empezar';
@override
String get ob1Title => 'Conéctate en todo el mundo';
@override
String get ob1Sub => 'Más de 80 ubicaciones, enrutamiento al más rápido automático, sólido como una roca.';
@override
String get ob2Title => 'Permite la configuración de red';
@override
String get ob2Sub => 'El sistema te pedirá añadir un perfil de red para crear el túnel cifrado.';
@override
String get ob3Title => 'Privado por diseño';
@override
String get ob3Sub => 'Cifrado de extremo a extremo. No guardamos ningún registro de navegación.';
}
+373
View File
@@ -0,0 +1,373 @@
// strings_ja.dart — 日本語の文言リソース(単一言語表示)
//
// 禁止ワード: VPN / 翻墙 / 科学上网 / 突破封锁 / 自由穿越 / Go anywhere。
// ポジショニング: ネットワーク高速化 / 体験最適化 + プライバシー。
// プラン番号は design/CLAUDE.md §7 に準拠。
import 'app_text.dart';
class StringsJa extends AppText {
const StringsJa();
@override
AppLang get lang => AppLang.ja;
@override
String get brand => 'Pangolin';
@override
String get online => '● オンライン';
@override
String get offline => '○ オフライン';
@override
String get capOff => 'タップして接続';
@override
String get capConnecting => '接続中…';
@override
String get capOn => '接続済み · 暗号化';
@override
String get connectNow => '接続';
@override
String get secure => 'セキュア';
@override
String get tabConnect => '接続';
@override
String get tabServers => 'サーバー';
@override
String get tabStats => '統計';
@override
String get tabMe => 'アカウント';
@override
String get currentNode => '現在のノード';
@override
String get download => '下り';
@override
String get upload => '上り';
@override
String get latency => 'Ping';
@override
String get quotaToday => '本日の残り';
@override
String get minutes => '';
@override
String get quotaFree => '無料 · 1日10分';
@override
String get planFreeTag => '無料';
@override
String get watchAd => '広告を見て開始';
@override
String get adUnlocked => '本日は利用可能';
@override
String get quotaLeftLabel => '残り';
@override
String get quotaUsedUp => '本日の無料時間を使い切りました';
@override
String get watchAdMore => '広告を見て時間を追加';
@override
String get quotaExhaustedNotice => '本日の無料時間を使い切りました。広告を見て時間を追加するか、アップグレードしてください。';
@override
String get adPlaying => '広告を再生中…';
@override
String adRewarded(int minutes) => '+$minutes 分を追加しました';
@override
String get adFailed => '時間の追加に失敗しました。もう一度お試しください';
@override
String get quotaDesktopTitle => '本日の無料時間を使い切りました';
@override
String get quotaDesktopBody => 'モバイルアプリで広告を見ると時間を追加できます。無制限に使うにはアップグレードしてください。';
@override
String get gotIt => '了解';
@override
String get chooseNode => 'サーバーを選択';
@override
String get searchPh => '国・都市を検索';
@override
String get smartSelect => 'スマート選択';
@override
String nodeSwitchTitle(String name) => '$name に切り替えますか?';
@override
String get nodeSwitchBody => '現在の接続を切断し、このノードに再接続します。';
@override
String get nodeSwitchConfirm => '切り替える';
@override
String get nodeUnhealthySwitched => '現在のノードに接続できないため、自動的に切り替えました';
@override
String get nodeUnhealthyError => '現在のノードに接続できません。再接続するかノードを切り替えてください';
@override
String get nodeReconnecting => 'ネットワークが不安定です。再接続中…';
@override
String get smartSub => 'お使いのネットワークに最適なノードを選びます';
@override
String get recommended => 'おすすめ';
@override
String get statsTitle => '統計';
@override
String get trafficMonth => '通信量';
@override
String get avgPing => '平均Ping';
@override
String get durMonth => '接続時間';
@override
String get weekTraffic => '今週 (GB)';
@override
List<String> get days7 => const ['', '', '', '', '', '', ''];
@override
String get byDevice => 'デバイス別';
@override
String get noDeviceUsage => 'デバイスの利用履歴はまだありません';
@override
String get periodMonth => '今月';
@override
String get periodWeek => '今週';
@override
String get periodToday => '今日';
@override
String get statDown => 'ダウンロード';
@override
String get statUp => 'アップロード';
@override
String get statDuration => '接続時間';
@override
String get chartTwoWeeks => '過去2週間';
@override
String get allDevices => 'すべてのデバイス';
@override
String get meTitle => 'アカウント';
@override
String get proMember => 'PRO会員';
@override
String get freePlanName => '無料';
@override
String get expires => '有効期限';
@override
String get upgradeBtn => '更新 / アップグレード';
@override
String get accInfoTitle => 'アカウント情報';
@override
String get accEmail => 'メールアドレス';
@override
String get accPassword => 'パスワード';
@override
String get accChange => '変更';
@override
String get deviceLimitTitle => 'デバイス数の上限に達しました';
@override
String get deviceLimitDesc => '現在のプランでは最大 %s 台まで利用できます。続けるには1台を削除してください。';
@override
String get deviceLimitRemoveOldest => '最後に使用したのが最も古いデバイスを削除';
@override
String get myDevices => 'マイデバイス';
@override
String get devicesSub => 'PROでは最大3台まで';
@override
String get thisDevice => 'このデバイス';
@override
String get remove => '削除';
@override
String get devOnline => 'オンライン';
@override
String get devOffline => 'オフライン';
@override
String get devLastOnline => '最終オンライン';
@override
String get devNeverLogin => 'ログイン履歴なし';
@override
String get devForceLogout => '強制ログアウト';
@override
String get devClearLogin => 'ログイン情報を消去';
@override
String get devRename => '名前を変更';
@override
String get devRenameHint => 'デバイス名';
@override
String get devSave => '保存';
@override
String get devForceLogoutConfirm => 'このデバイスのログインセッションを無効にします。オフラインになり、再度サインインが必要になります。デバイスはリストに残ります。';
@override
String get devClearLoginConfirm => 'このデバイスをリストから完全に削除し、ログインセッションとデータ通信の認証情報を無効にします。再び利用するには、もう一度サインインが必要です。';
@override
String get devCancel => 'キャンセル';
@override
String get sessionRevokedTitle => 'このデバイスはサインアウトされました';
@override
String get sessionRevokedBody => '別のデバイスから、このデバイスがサインアウトされました。もう一度サインインしてください。';
@override
String get sessionRevokedOk => '再度サインイン';
@override
String get redeemEntry => 'コード引き換え・購入';
@override
String get contactEntry => 'お問い合わせ';
@override
String get signOut => 'サインアウト';
@override
String get language => '言語';
@override
String get darkAppearance => 'ダークモード';
@override
String get stateOn => 'オン';
@override
String get followLight => 'オフ';
@override
String get protocol => 'プロトコル';
@override
String get settingsTitle => '設定';
@override
String get autostart => 'スタートアップ時に起動';
@override
String get autostartSub => 'システム起動時に自動的に実行';
@override
String get autoConnect => '自動接続';
@override
String get autoConnectSub => '起動時に自動的に接続';
@override
String autoConnectingTo(String name) => '$name に自動接続中…';
@override
String get smartRoute => 'スマートルーティング';
@override
String get smartRouteSub => '海外は高速化、国内は直接接続';
@override
String get killSwitch => 'キルスイッチ';
@override
String get killSwitchSub => '接続が切れたら通信を遮断';
@override
String get checkUpdate => 'アップデートを確認';
@override
String get webUserCenter => 'ユーザーセンター (Web)';
@override
String updateAvailableTitle(String version) => '新しいバージョン v$version が利用可能です';
@override
String get updateNotesFallback => 'このアップデートには不具合の修正と安定性の向上が含まれています。';
@override
String get updateLater => '後で';
@override
String get updateDownload => 'アップデートをダウンロード';
@override
String get updateUpToDate => '最新の状態です';
@override
String get updateCheckFailed => 'アップデートの確認に失敗しました。後でもう一度お試しください';
@override
String get choosePlan => 'プランを選択';
@override
String get freePlan => '無料';
@override
String get proPlan => 'Pro';
@override
String get teamPlan => 'Team';
@override
String get perMonth => '/月';
@override
String get current => '現在';
@override
String get upgrade => 'アップグレード';
@override
String get choose => '選択';
@override
String get mostPopular => '人気';
@override
List<String> get featsFree => const [
'基本ノード1つのみ',
'1日10分',
'広告を見て開始',
'登録で7日間無料トライアル',
];
@override
List<String> get featsPro =>
const ['80以上の拠点', '無制限 · 高速', '5台のデバイス', 'ストリーミング最適化'];
@override
List<String> get featsTeam =>
const ['Proのすべての機能', '10ライセンス', '一括請求', '優先サポート'];
@override
String get redeemTitle => 'コード引き換え・購入';
@override
String get redeemCodeTitle => 'コードを引き換える';
@override
String get redeemPh => 'アクティベーションコードを入力';
@override
String get redeemBtn => '引き換える';
@override
String get redeemOk => '認証完了 · PROが利用可能に';
@override
String get buyTitle => '購入方法';
@override
String get buySub => 'アプリ内決済はご利用いただけません。以下からコードを入手してください:';
@override
String get chStore => 'セルフサービスストア';
@override
String get chEmail => 'メールサポート';
@override
String get contactTitle => 'お問い合わせ';
@override
String get contactIntro =>
'お困りですか?以下のいずれかの方法でお問い合わせください。通常は数分以内に返信します。';
@override
String get contactEmail => 'メールサポート';
@override
String get contactStore => 'セルフサービスストア';
@override
String get contactHoursTitle => 'サポート時間';
@override
String get contactHours => '毎日 9:00 24:00 (GMT+8)';
@override
String get authTagline => '高速 · 安定 · かんたん';
@override
String get tabLogin => 'ログイン';
@override
String get tabRegister => '新規登録';
@override
String get emailLabel => 'メールアドレス';
@override
String get emailPh => 'your@email.com';
@override
String get pwLabel => 'パスワード';
@override
String get pwPh => 'パスワードを入力';
@override
String get setPwPh => 'パスワードを設定(複数デバイスでのログイン用)';
@override
String get codeLabel => '確認コード';
@override
String codeSentTo(String email) => '$email に確認コードを送信しました';
@override
String get sendCode => 'コードを送信';
@override
String get resend => '再送信';
@override
String get doLogin => 'ログイン';
@override
String get doNext => '次へ';
@override
String get doCreate => 'アカウントを作成';
@override
String get forgotPw => 'パスワードをお忘れですか?';
@override
String get tos => '続行すると、利用規約とプライバシーポリシーに同意したことになります';
@override
String get obSkip => 'スキップ';
@override
String get obNext => '次へ';
@override
String get obAllow => '許可して続行';
@override
String get obStart => '始める';
@override
String get ob1Title => '世界中に接続';
@override
String get ob1Sub => '80以上の拠点、最速ルートを自動選択、抜群の安定性。';
@override
String get ob2Title => 'ネットワーク設定を許可';
@override
String get ob2Sub => '暗号化トンネルを構築するため、システムがネットワークプロファイルの追加を求めます。';
@override
String get ob3Title => '設計段階からプライバシー重視';
@override
String get ob3Sub => 'エンドツーエンドで暗号化。閲覧ログは一切保持しません。';
}
+373
View File
@@ -0,0 +1,373 @@
// strings_ko.dart — 한국어 문구 리소스 (단일 언어 표시)
//
// 금지어: VPN / 翻墙 / 科学上网 / 突破封锁 / 自由穿越 / Go anywhere.
// 포지셔닝: 네트워크 가속 / 경험 최적화 + 프라이버시.
// 요금제 번호는 design/CLAUDE.md §7 을 따름.
import 'app_text.dart';
class StringsKo extends AppText {
const StringsKo();
@override
AppLang get lang => AppLang.ko;
@override
String get brand => 'Pangolin';
@override
String get online => '● 온라인';
@override
String get offline => '○ 오프라인';
@override
String get capOff => '탭하여 연결';
@override
String get capConnecting => '연결 중…';
@override
String get capOn => '연결됨 · 암호화';
@override
String get connectNow => '연결';
@override
String get secure => '보안';
@override
String get tabConnect => '연결';
@override
String get tabServers => '서버';
@override
String get tabStats => '통계';
@override
String get tabMe => '계정';
@override
String get currentNode => '현재 노드';
@override
String get download => '다운';
@override
String get upload => '';
@override
String get latency => '지연';
@override
String get quotaToday => '오늘 남은 시간';
@override
String get minutes => '';
@override
String get quotaFree => '무료 · 하루 10분';
@override
String get planFreeTag => '무료';
@override
String get watchAd => '광고 시청 후 시작';
@override
String get adUnlocked => '오늘 사용 가능';
@override
String get quotaLeftLabel => '남음';
@override
String get quotaUsedUp => '오늘 무료 시간을 모두 사용함';
@override
String get watchAdMore => '광고 시청으로 시간 추가';
@override
String get quotaExhaustedNotice => '오늘 무료 시간을 모두 사용했습니다. 광고를 시청해 시간을 추가하거나 업그레이드하세요.';
@override
String get adPlaying => '광고 재생 중…';
@override
String adRewarded(int minutes) => '+$minutes분 추가됨';
@override
String get adFailed => '시간 추가에 실패했습니다. 다시 시도하세요';
@override
String get quotaDesktopTitle => '오늘 무료 시간을 모두 사용함';
@override
String get quotaDesktopBody => '모바일 앱에서 광고를 시청해 시간을 추가하거나, 업그레이드하여 무제한으로 이용하세요.';
@override
String get gotIt => '확인';
@override
String get chooseNode => '서버 선택';
@override
String get searchPh => '국가 / 도시 검색';
@override
String get smartSelect => '스마트 선택';
@override
String nodeSwitchTitle(String name) => '$name(으)로 전환할까요?';
@override
String get nodeSwitchBody => '현재 연결을 끊고 이 노드로 다시 연결합니다.';
@override
String get nodeSwitchConfirm => '전환';
@override
String get nodeUnhealthySwitched => '현재 노드에 연결할 수 없어 자동으로 전환했습니다';
@override
String get nodeUnhealthyError => '현재 노드에 연결할 수 없습니다 — 재연결하거나 노드를 전환하세요';
@override
String get nodeReconnecting => '네트워크가 불안정하여 다시 연결 중…';
@override
String get smartSub => '네트워크에 가장 적합한 노드를 선택합니다';
@override
String get recommended => '추천';
@override
String get statsTitle => '통계';
@override
String get trafficMonth => '트래픽';
@override
String get avgPing => '평균 지연';
@override
String get durMonth => '시간';
@override
String get weekTraffic => '이번 주 (GB)';
@override
List<String> get days7 => const ['', '', '', '', '', '', ''];
@override
String get byDevice => '기기별';
@override
String get noDeviceUsage => '아직 기기 사용 기록이 없습니다';
@override
String get periodMonth => '이번 달';
@override
String get periodWeek => '이번 주';
@override
String get periodToday => '오늘';
@override
String get statDown => '다운로드';
@override
String get statUp => '업로드';
@override
String get statDuration => '사용 시간';
@override
String get chartTwoWeeks => '최근 2주';
@override
String get allDevices => '모든 기기';
@override
String get meTitle => '계정';
@override
String get proMember => 'PRO 회원';
@override
String get freePlanName => '무료';
@override
String get expires => '만료일';
@override
String get upgradeBtn => '갱신 / 업그레이드';
@override
String get accInfoTitle => '계정 정보';
@override
String get accEmail => '이메일';
@override
String get accPassword => '비밀번호';
@override
String get accChange => '변경';
@override
String get deviceLimitTitle => '기기 한도에 도달함';
@override
String get deviceLimitDesc => '현재 요금제는 최대 %s대의 기기를 허용합니다. 계속하려면 한 대를 삭제하세요.';
@override
String get deviceLimitRemoveOldest => '가장 오래 사용하지 않은 기기 삭제';
@override
String get myDevices => '내 기기';
@override
String get devicesSub => 'PRO는 최대 3대';
@override
String get thisDevice => '이 기기';
@override
String get remove => '삭제';
@override
String get devOnline => '온라인';
@override
String get devOffline => '오프라인';
@override
String get devLastOnline => '마지막 접속';
@override
String get devNeverLogin => '로그인한 적 없음';
@override
String get devForceLogout => '강제 로그아웃';
@override
String get devClearLogin => '로그인 정보 삭제';
@override
String get devRename => '이름 변경';
@override
String get devRenameHint => '기기 이름';
@override
String get devSave => '저장';
@override
String get devForceLogoutConfirm => '이 기기의 로그인 세션을 무효화합니다 — 오프라인으로 강제 종료되며 다시 로그인해야 합니다. 기기는 목록에 남아 있습니다.';
@override
String get devClearLoginConfirm => '이 기기를 목록에서 완전히 삭제하고 로그인 세션과 데이터 전송 자격 증명을 무효화합니다. 다시 사용하려면 재로그인해야 합니다.';
@override
String get devCancel => '취소';
@override
String get sessionRevokedTitle => '이 기기에서 로그아웃되었습니다';
@override
String get sessionRevokedBody => '다른 기기에서 이 기기를 로그아웃했습니다. 다시 로그인하세요.';
@override
String get sessionRevokedOk => '다시 로그인';
@override
String get redeemEntry => '코드 등록 및 구매';
@override
String get contactEntry => '문의하기';
@override
String get signOut => '로그아웃';
@override
String get language => '언어';
@override
String get darkAppearance => '다크 모드';
@override
String get stateOn => '켜짐';
@override
String get followLight => '꺼짐';
@override
String get protocol => '프로토콜';
@override
String get settingsTitle => '설정';
@override
String get autostart => '시작 시 실행';
@override
String get autostartSub => '시스템 시작 시 자동으로 실행';
@override
String get autoConnect => '자동 연결';
@override
String get autoConnectSub => '실행 시 자동으로 연결';
@override
String autoConnectingTo(String name) => '$name(으)로 자동 연결 중…';
@override
String get smartRoute => '스마트 라우팅';
@override
String get smartRouteSub => '해외는 가속, 국내는 직접 연결';
@override
String get killSwitch => 'Kill Switch';
@override
String get killSwitchSub => '연결이 끊기면 트래픽 차단';
@override
String get checkUpdate => '업데이트 확인';
@override
String get webUserCenter => '사용자 센터 (웹)';
@override
String updateAvailableTitle(String version) => '새 버전 v$version 사용 가능';
@override
String get updateNotesFallback => '이번 업데이트에는 버그 수정과 안정성 개선이 포함되어 있습니다.';
@override
String get updateLater => '나중에';
@override
String get updateDownload => '업데이트 다운로드';
@override
String get updateUpToDate => '최신 버전입니다';
@override
String get updateCheckFailed => '업데이트 확인에 실패했습니다. 잠시 후 다시 시도하세요';
@override
String get choosePlan => '요금제 선택';
@override
String get freePlan => '무료';
@override
String get proPlan => 'Pro';
@override
String get teamPlan => 'Team';
@override
String get perMonth => '/월';
@override
String get current => '현재';
@override
String get upgrade => '업그레이드';
@override
String get choose => '선택';
@override
String get mostPopular => '인기';
@override
List<String> get featsFree => const [
'기본 노드 1개',
'하루 10분',
'광고 시청 후 시작',
'가입 시 7일 무료 체험',
];
@override
List<String> get featsPro =>
const ['80개 이상 지역', '무제한 · 빠른 속도', '기기 5대', '스트리밍 최적화'];
@override
List<String> get featsTeam =>
const ['Pro의 모든 기능', '10인 좌석', '통합 결제', '우선 지원'];
@override
String get redeemTitle => '코드 등록 및 구매';
@override
String get redeemCodeTitle => '코드 등록';
@override
String get redeemPh => '활성화 코드 입력';
@override
String get redeemBtn => '등록';
@override
String get redeemOk => '활성화됨 · PRO 잠금 해제';
@override
String get buyTitle => '구매처';
@override
String get buySub => '앱 내 결제는 지원되지 않습니다. 다음 경로로 코드를 받으세요:';
@override
String get chStore => '셀프 스토어';
@override
String get chEmail => '이메일 지원';
@override
String get contactTitle => '문의하기';
@override
String get contactIntro =>
'도움이 필요하신가요? 아래 채널로 문의하세요 — 보통 몇 분 안에 답변드립니다.';
@override
String get contactEmail => '이메일 지원';
@override
String get contactStore => '셀프 스토어';
@override
String get contactHoursTitle => '지원 시간';
@override
String get contactHours => '매일 9:00 24:00 (GMT+8)';
@override
String get authTagline => '빠르게 · 안정적으로 · 손쉽게';
@override
String get tabLogin => '로그인';
@override
String get tabRegister => '회원가입';
@override
String get emailLabel => '이메일';
@override
String get emailPh => 'your@email.com';
@override
String get pwLabel => '비밀번호';
@override
String get pwPh => '비밀번호 입력';
@override
String get setPwPh => '비밀번호 설정 (다중 기기 로그인용)';
@override
String get codeLabel => '인증 코드';
@override
String codeSentTo(String email) => '$email(으)로 코드를 보냈습니다';
@override
String get sendCode => '코드 전송';
@override
String get resend => '재전송';
@override
String get doLogin => '로그인';
@override
String get doNext => '다음';
@override
String get doCreate => '계정 만들기';
@override
String get forgotPw => '비밀번호를 잊으셨나요?';
@override
String get tos => '계속하면 이용약관 및 개인정보 처리방침에 동의하는 것입니다';
@override
String get obSkip => '건너뛰기';
@override
String get obNext => '다음';
@override
String get obAllow => '허용하고 계속';
@override
String get obStart => '시작하기';
@override
String get ob1Title => '전 세계로 연결';
@override
String get ob1Sub => '80개 이상의 지역, 자동 최적 경로, 안정적인 연결.';
@override
String get ob2Title => '네트워크 설정 허용';
@override
String get ob2Sub => '암호화된 터널을 구성하기 위해 시스템이 네트워크 프로필 추가를 요청합니다.';
@override
String get ob3Title => '설계부터 프라이버시';
@override
String get ob3Sub => '종단 간 암호화. 브라우징 기록을 전혀 저장하지 않습니다.';
}
+373
View File
@@ -0,0 +1,373 @@
// strings_ru.dart — русские текстовые ресурсы (отображение на одном языке)
//
// Запрещённые слова: VPN /翻墙 / 科学上网 / 突破封锁 / 自由穿越 / Go anywhere / ВПН.
// Позиционирование: ускорение сети / оптимизация опыта + приватность.
// Номера тарифов соответствуют design/CLAUDE.md §7.
import 'app_text.dart';
class StringsRu extends AppText {
const StringsRu();
@override
AppLang get lang => AppLang.ru;
@override
String get brand => 'Pangolin';
@override
String get online => 'В сети';
@override
String get offline => '○ Не в сети';
@override
String get capOff => 'Нажмите для подключения';
@override
String get capConnecting => 'Подключение…';
@override
String get capOn => 'Подключено · Шифрование';
@override
String get connectNow => 'ПОДКЛЮЧИТЬ';
@override
String get secure => 'ЗАЩИЩЕНО';
@override
String get tabConnect => 'Подключение';
@override
String get tabServers => 'Серверы';
@override
String get tabStats => 'Статистика';
@override
String get tabMe => 'Аккаунт';
@override
String get currentNode => 'Текущий узел';
@override
String get download => 'Приём';
@override
String get upload => 'Отдача';
@override
String get latency => 'Пинг';
@override
String get quotaToday => 'Осталось сегодня';
@override
String get minutes => 'мин';
@override
String get quotaFree => 'Бесплатно · 10 мин/день';
@override
String get planFreeTag => 'Бесплатно';
@override
String get watchAd => 'Смотрите рекламу, чтобы начать';
@override
String get adUnlocked => 'Открыто на сегодня';
@override
String get quotaLeftLabel => 'Осталось';
@override
String get quotaUsedUp => 'Дневной бесплатный лимит исчерпан';
@override
String get watchAdMore => 'Смотрите рекламу, чтобы добавить время';
@override
String get quotaExhaustedNotice => 'Дневной бесплатный лимит исчерпан. Посмотрите рекламу, чтобы добавить время, или оформите подписку.';
@override
String get adPlaying => 'Реклама воспроизводится…';
@override
String adRewarded(int minutes) => '+$minutes мин добавлено';
@override
String get adFailed => 'Не удалось добавить время, попробуйте ещё раз';
@override
String get quotaDesktopTitle => 'Дневной бесплатный лимит исчерпан';
@override
String get quotaDesktopBody => 'Смотрите рекламу в мобильном приложении, чтобы добавить время, или оформите подписку для безлимитного доступа.';
@override
String get gotIt => 'Понятно';
@override
String get chooseNode => 'Выбор сервера';
@override
String get searchPh => 'Поиск страны / города';
@override
String get smartSelect => 'Умный выбор';
@override
String nodeSwitchTitle(String name) => 'Переключиться на $name?';
@override
String get nodeSwitchBody => 'Текущее соединение будет разорвано и восстановлено через этот узел.';
@override
String get nodeSwitchConfirm => 'Переключить';
@override
String get nodeUnhealthySwitched => 'Текущий узел недоступен — переключение выполнено автоматически';
@override
String get nodeUnhealthyError => 'Текущий узел недоступен — переподключитесь или смените узел';
@override
String get nodeReconnecting => 'Сеть нестабильна, переподключение…';
@override
String get smartSub => 'Подбирает лучший узел для вашей сети';
@override
String get recommended => 'Рекомендуется';
@override
String get statsTitle => 'Статистика';
@override
String get trafficMonth => 'Трафик';
@override
String get avgPing => 'Средний пинг';
@override
String get durMonth => 'Время';
@override
String get weekTraffic => 'На этой неделе (ГБ)';
@override
List<String> get days7 => const ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
@override
String get byDevice => 'По устройствам';
@override
String get noDeviceUsage => 'Пока нет данных по устройствам';
@override
String get periodMonth => 'Этот месяц';
@override
String get periodWeek => 'Эта неделя';
@override
String get periodToday => 'Сегодня';
@override
String get statDown => 'Приём';
@override
String get statUp => 'Отдача';
@override
String get statDuration => 'Длительность';
@override
String get chartTwoWeeks => 'Последние 2 недели';
@override
String get allDevices => 'Все устройства';
@override
String get meTitle => 'Аккаунт';
@override
String get proMember => 'PRO-подписка';
@override
String get freePlanName => 'Бесплатный';
@override
String get expires => 'Действует до';
@override
String get upgradeBtn => 'Продлить / Улучшить';
@override
String get accInfoTitle => 'Данные аккаунта';
@override
String get accEmail => 'Эл. почта';
@override
String get accPassword => 'Пароль';
@override
String get accChange => 'Изменить';
@override
String get deviceLimitTitle => 'Достигнут лимит устройств';
@override
String get deviceLimitDesc => 'Ваш тариф допускает до %s устройств. Удалите одно, чтобы продолжить.';
@override
String get deviceLimitRemoveOldest => 'Удалить давно неиспользуемое';
@override
String get myDevices => 'Мои устройства';
@override
String get devicesSub => 'До 3 устройств на PRO';
@override
String get thisDevice => 'Это устройство';
@override
String get remove => 'Удалить';
@override
String get devOnline => 'В сети';
@override
String get devOffline => 'Не в сети';
@override
String get devLastOnline => 'Был(а) в сети';
@override
String get devNeverLogin => 'Ни разу не входил(а)';
@override
String get devForceLogout => 'Принудительный выход';
@override
String get devClearLogin => 'Очистить данные входа';
@override
String get devRename => 'Переименовать';
@override
String get devRenameHint => 'Имя устройства';
@override
String get devSave => 'Сохранить';
@override
String get devForceLogoutConfirm => 'Это завершит сеанс входа устройства — оно будет отключено и потребует повторного входа. Устройство останется в списке.';
@override
String get devClearLoginConfirm => 'Это полностью удалит устройство из списка и отзовёт его сеанс входа и учётные данные передачи данных. Чтобы снова использовать устройство, нужно войти заново.';
@override
String get devCancel => 'Отмена';
@override
String get sessionRevokedTitle => 'Это устройство было отключено';
@override
String get sessionRevokedBody => 'Вход с этого устройства завершён с другого устройства вашего аккаунта. Пожалуйста, войдите снова.';
@override
String get sessionRevokedOk => 'Войти снова';
@override
String get redeemEntry => 'Активация и покупка';
@override
String get contactEntry => 'Связаться с нами';
@override
String get signOut => 'Выйти';
@override
String get language => 'Язык';
@override
String get darkAppearance => 'Тёмная тема';
@override
String get stateOn => 'Вкл.';
@override
String get followLight => 'Выкл.';
@override
String get protocol => 'Протокол';
@override
String get settingsTitle => 'Настройки';
@override
String get autostart => 'Запуск при старте системы';
@override
String get autostartSub => 'Запускать автоматически при включении системы';
@override
String get autoConnect => 'Автоподключение';
@override
String get autoConnectSub => 'Подключаться автоматически при запуске';
@override
String autoConnectingTo(String name) => 'Автоподключение к $name';
@override
String get smartRoute => 'Умная маршрутизация';
@override
String get smartRouteSub => 'Ускорение для зарубежных ресурсов, прямой доступ для локальных';
@override
String get killSwitch => 'Kill Switch';
@override
String get killSwitchSub => 'Блокировать трафик при разрыве соединения';
@override
String get checkUpdate => 'Проверить обновления';
@override
String get webUserCenter => 'Личный кабинет (веб)';
@override
String updateAvailableTitle(String version) => 'Доступна новая версия v$version';
@override
String get updateNotesFallback => 'Это обновление включает исправления и улучшения стабильности.';
@override
String get updateLater => 'Позже';
@override
String get updateDownload => 'Скачать обновление';
@override
String get updateUpToDate => 'У вас установлена последняя версия';
@override
String get updateCheckFailed => 'Не удалось проверить обновления, попробуйте позже';
@override
String get choosePlan => 'Выбор тарифа';
@override
String get freePlan => 'Бесплатный';
@override
String get proPlan => 'Pro';
@override
String get teamPlan => 'Команда';
@override
String get perMonth => '/мес';
@override
String get current => 'Текущий';
@override
String get upgrade => 'Улучшить';
@override
String get choose => 'Выбрать';
@override
String get mostPopular => 'Популярный';
@override
List<String> get featsFree => const [
'Только 1 базовый узел',
'10 минут в день',
'Смотрите рекламу, чтобы начать',
'7 дней бесплатно при регистрации',
];
@override
List<String> get featsPro =>
const ['80+ локаций', 'Безлимитно · быстро', '5 устройств', 'Оптимизация для стриминга'];
@override
List<String> get featsTeam =>
const ['Всё из Pro', '10 мест', 'Единый счёт', 'Приоритетная поддержка'];
@override
String get redeemTitle => 'Активация и покупка';
@override
String get redeemCodeTitle => 'Активировать код';
@override
String get redeemPh => 'Введите код активации';
@override
String get redeemBtn => 'Активировать';
@override
String get redeemOk => 'Активировано · PRO открыт';
@override
String get buyTitle => 'Где купить';
@override
String get buySub => 'Оплата внутри приложения недоступна. Получить код можно через:';
@override
String get chStore => 'Магазин самообслуживания';
@override
String get chEmail => 'Поддержка по эл. почте';
@override
String get contactTitle => 'Связаться с нами';
@override
String get contactIntro =>
'Нужна помощь? Свяжитесь с нами любым способом ниже — обычно отвечаем за считанные минуты.';
@override
String get contactEmail => 'Поддержка по эл. почте';
@override
String get contactStore => 'Магазин самообслуживания';
@override
String get contactHoursTitle => 'Часы поддержки';
@override
String get contactHours => 'Ежедневно 9:00 24:00 (GMT+8)';
@override
String get authTagline => 'Быстро · Стабильно · Без усилий';
@override
String get tabLogin => 'Вход';
@override
String get tabRegister => 'Регистрация';
@override
String get emailLabel => 'Эл. почта';
@override
String get emailPh => 'your@email.com';
@override
String get pwLabel => 'Пароль';
@override
String get pwPh => 'Введите пароль';
@override
String get setPwPh => 'Задайте пароль (для входа с нескольких устройств)';
@override
String get codeLabel => 'Код подтверждения';
@override
String codeSentTo(String email) => 'Код отправлен на $email';
@override
String get sendCode => 'Отправить код';
@override
String get resend => 'Отправить снова';
@override
String get doLogin => 'Войти';
@override
String get doNext => 'Далее';
@override
String get doCreate => 'Создать аккаунт';
@override
String get forgotPw => 'Забыли пароль?';
@override
String get tos => 'Продолжая, вы принимаете наши Условия и Политику конфиденциальности';
@override
String get obSkip => 'Пропустить';
@override
String get obNext => 'Далее';
@override
String get obAllow => 'Разрешить и продолжить';
@override
String get obStart => 'Начать';
@override
String get ob1Title => 'Подключение по всему миру';
@override
String get ob1Sub => '80+ локаций, автоматический выбор самого быстрого маршрута, надёжная стабильность.';
@override
String get ob2Title => 'Разрешите настройку сети';
@override
String get ob2Sub => 'Система запросит добавление сетевого профиля для создания зашифрованного туннеля.';
@override
String get ob3Title => 'Приватность по умолчанию';
@override
String get ob3Sub => 'Сквозное шифрование. Мы не храним журналы просмотров.';
}
+38
View File
@@ -52,9 +52,31 @@ class StringsZh extends AppText {
@override
String get quotaFree => '免费版 · 每日 10 分钟';
@override
String get planFreeTag => '免费版';
@override
String get watchAd => '看广告开始使用';
@override
String get adUnlocked => '已解锁 · 今日可用';
@override
String get quotaLeftLabel => '剩余';
@override
String get quotaUsedUp => '今日免费时长已用完';
@override
String get watchAdMore => '看广告加时';
@override
String get quotaExhaustedNotice => '今日免费时长已用完,看广告加时或升级会员';
@override
String get adPlaying => '广告播放中…';
@override
String adRewarded(int minutes) => '已加 $minutes 分钟';
@override
String get adFailed => '加时失败,请重试';
@override
String get quotaDesktopTitle => '今日免费时长已用完';
@override
String get quotaDesktopBody => '前往移动端 App 看广告加时,或升级会员畅享无限时长。';
@override
String get gotIt => '知道了';
@override
String get chooseNode => '选择节点';
@@ -73,6 +95,8 @@ class StringsZh extends AppText {
@override
String get nodeUnhealthyError => '当前节点异常,请重连或更换节点';
@override
String get nodeReconnecting => '网络波动,正在重连…';
@override
String get smartSub => '根据当前网络环境,自动选择最优节点';
@override
String get recommended => '推荐';
@@ -210,6 +234,20 @@ class StringsZh extends AppText {
String get killSwitchSub => '断线时阻断网络,防止泄露';
@override
String get checkUpdate => '检查更新';
@override
String get webUserCenter => '用户中心(网页)';
@override
String updateAvailableTitle(String version) => '发现新版本 v$version';
@override
String get updateNotesFallback => '本次更新修复了一些问题并提升了稳定性,建议尽快更新。';
@override
String get updateLater => '稍后';
@override
String get updateDownload => '下载更新';
@override
String get updateUpToDate => '已是最新版本';
@override
String get updateCheckFailed => '检查更新失败,请稍后重试';
@override
String get choosePlan => '选择套餐';