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
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user