From 13e0ba6d47cae49b52eea0c08320dfdd2cc6388a Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 27 Jul 2026 08:55:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E2=91=A3=20l10n=20=E6=BC=82?= =?UTF-8?q?=E7=A7=BB=E9=97=B8=20+=20codegen=20=E6=94=B9=E7=BA=AF=20node=20?= =?UTF-8?q?=E7=A1=AE=E5=AE=9A=E6=80=A7=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gen_l10n_dart.mjs 去掉 dart format:其输出依赖包解析状态、跨环境不稳(首次 4 空格、 重跑 8 空格),会让漂移闸误报。改为确定性终态格式(list 单行 const [])。 analyze 不校验格式,不影响;gen 现零 dart 依赖、纯 node 确定。 - ci/check-codegen-drift.sh 增 l10n 段:重生成 strings_*.dart → git diff 非空即 fail (与 token 段同法,纯 node)。 - 验证:往返无损、重生成零 diff(闸稳定)、flutter analyze 0 issue、244 测试全绿。 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo --- ci/check-codegen-drift.sh | 15 ++++++ client/lib/l10n/strings_en.dart | 69 +++++++----------------- client/lib/l10n/strings_es.dart | 93 ++++++++++---------------------- client/lib/l10n/strings_ja.dart | 33 +++--------- client/lib/l10n/strings_ko.dart | 33 +++--------- client/lib/l10n/strings_ru.dart | 93 ++++++++++---------------------- client/lib/l10n/strings_zh.dart | 24 ++------- design/codegen/gen_l10n_dart.mjs | 13 ++--- 8 files changed, 110 insertions(+), 263 deletions(-) diff --git a/ci/check-codegen-drift.sh b/ci/check-codegen-drift.sh index 1187f77..4c686bf 100755 --- a/ci/check-codegen-drift.sh +++ b/ci/check-codegen-drift.sh @@ -31,3 +31,18 @@ if ! git diff --quiet -- "$GEN"; then fi echo "✅ codegen 无漂移:token 生成物与 CSS 真相源一致。" + +# ── l10n 单源:design/i18n/strings.json → client/lib/l10n/strings_*.dart ── +# gen 为纯 node 确定性输出(不跑 dart format),故本段与 token 段一样零 dart 依赖。 +L10N_GEN="client/lib/l10n" +echo "→ 重新生成 l10n (design/codegen/gen_l10n_dart.mjs) ..." +node design/codegen/gen_l10n_dart.mjs +if ! git diff --quiet -- "$L10N_GEN"/strings_*.dart; then + echo "❌ l10n 漂移:strings_*.dart 与 design/i18n/strings.json 不一致。" >&2 + echo " 原因:改了 strings.json 却没重生成,或手改了生成物(勿手改 strings_*.dart)。" >&2 + echo " 修复:node design/codegen/gen_l10n_dart.mjs 后提交生成物。" >&2 + echo " ── diff(前 40 行)──" >&2 + git --no-pager diff -- "$L10N_GEN"/strings_*.dart | head -40 >&2 + exit 1 +fi +echo "✅ l10n 无漂移:strings_*.dart 与 strings.json 单源一致。" diff --git a/client/lib/l10n/strings_en.dart b/client/lib/l10n/strings_en.dart index 7f60d33..5644b52 100644 --- a/client/lib/l10n/strings_en.dart +++ b/client/lib/l10n/strings_en.dart @@ -61,8 +61,7 @@ class StringsEn extends AppText { @override String get watchAdMore => 'Watch ad for more time'; @override - String get quotaExhaustedNotice => - 'You\'ve used today\'s free time. Upgrade to keep connecting.'; + String get quotaExhaustedNotice => 'You\'ve used today\'s free time. Upgrade to keep connecting.'; @override String get adPlaying => 'Ad playing…'; @override @@ -70,8 +69,7 @@ class StringsEn extends AppText { @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.'; + String get quotaDesktopBody => 'Watch ads in the mobile app to add time, or upgrade for unlimited access.'; @override String get gotIt => 'Got it'; @override @@ -81,16 +79,13 @@ class StringsEn extends AppText { @override String get smartSelect => 'Smart select'; @override - String get nodeSwitchBody => - 'This disconnects the current link and reconnects to this node.'; + String get nodeSwitchBody => 'This disconnects the current link and reconnects to this node.'; @override String get nodeSwitchConfirm => 'Switch'; @override - String get nodeUnhealthySwitched => - 'Current node unreachable — switched automatically'; + String get nodeUnhealthySwitched => 'Current node unreachable — switched automatically'; @override - String get nodeUnhealthyError => - 'Current node is unreachable — reconnect or switch node'; + String get nodeUnhealthyError => 'Current node is unreachable — reconnect or switch node'; @override String get nodeReconnecting => 'Network unstable, reconnecting…'; @override @@ -148,8 +143,7 @@ class StringsEn extends AppText { @override String get deviceLimitTitle => 'Device limit reached'; @override - String get deviceLimitDesc => - 'Your plan allows up to %s devices. Remove one to continue.'; + String get deviceLimitDesc => 'Your plan allows up to %s devices. Remove one to continue.'; @override String get deviceLimitRemoveOldest => 'Remove least recently used'; @override @@ -179,18 +173,15 @@ class StringsEn extends AppText { @override String get devSave => 'Save'; @override - String get devForceLogoutConfirm => - 'This revokes the device\'s login session — it will be kicked offline and must sign in again. The device stays in the list.'; + String get devForceLogoutConfirm => 'This revokes the device\'s login session — it will be kicked offline and must sign in again. The device stays in the list.'; @override - String get devClearLoginConfirm => - 'This removes the device from the list entirely and revokes its login session and data-plane credential. The device must sign in again to be used.'; + String get devClearLoginConfirm => 'This removes the device from the list entirely and revokes its login session and data-plane credential. The device must sign in again to be used.'; @override String get devCancel => 'Cancel'; @override String get sessionRevokedTitle => 'This device was signed out'; @override - String get sessionRevokedBody => - 'Your account signed this device out from another device. Please sign in again.'; + String get sessionRevokedBody => 'Your account signed this device out from another device. Please sign in again.'; @override String get sessionRevokedOk => 'Sign in again'; @override @@ -232,8 +223,7 @@ class StringsEn extends AppText { @override String get webUserCenter => 'User Center (Web)'; @override - String get updateNotesFallback => - 'This update includes fixes and stability improvements.'; + String get updateNotesFallback => 'This update includes fixes and stability improvements.'; @override String get updateLater => 'Later'; @override @@ -279,8 +269,7 @@ class StringsEn extends AppText { @override String get contactTitle => 'Contact us'; @override - String get contactIntro => - 'Need help? Reach us via any channel below — usually replies in minutes.'; + String get contactIntro => 'Need help? Reach us via any channel below — usually replies in minutes.'; @override String get contactEmail => 'Email support'; @override @@ -338,8 +327,7 @@ class StringsEn extends AppText { @override String get ob2Title => 'Allow network setup'; @override - String get ob2Sub => - 'The system will ask to add a network profile to build the encrypted tunnel.'; + String get ob2Sub => 'The system will ask to add a network profile to build the encrypted tunnel.'; @override String get ob3Title => 'Private by design'; @override @@ -373,18 +361,15 @@ class StringsEn extends AppText { @override String get payNetworkLabel => 'Network'; @override - String get payExactAmountHint => - 'Send the exact amount; activates automatically'; + String get payExactAmountHint => 'Send the exact amount; activates automatically'; @override String get copied => 'Copied'; @override String get openAlipay => 'Pay with Alipay'; @override - String get openAlipayHint => - 'Return after paying; this page refreshes automatically'; + String get openAlipayHint => 'Return after paying; this page refreshes automatically'; @override - String get openAlipayFailed => - 'Couldn\'t open Alipay; check it\'s installed or try again'; + String get openAlipayFailed => 'Couldn\'t open Alipay; check it\'s installed or try again'; @override String get awaitingPayment => 'Awaiting payment'; @override @@ -520,26 +505,11 @@ class StringsEn extends AppText { @override List get days7 => const ['M', 'T', 'W', 'T', 'F', 'S', 'S']; @override - List get featsFree => const [ - '1 basic node only', - '10 min per day', - 'Watch an ad to start', - '7-day free trial on sign-up', - ]; + List get featsFree => const ['1 basic node only', '10 min per day', 'Watch an ad to start', '7-day free trial on sign-up']; @override - List get featsPro => const [ - '80+ locations', - 'Unlimited · fast', - '5 devices', - 'Streaming optimized', - ]; + List get featsPro => const ['80+ locations', 'Unlimited · fast', '5 devices', 'Streaming optimized']; @override - List get featsTeam => const [ - 'Everything in Pro', - '10 seats', - 'Central billing', - 'Priority support', - ]; + List get featsTeam => const ['Everything in Pro', '10 seats', 'Central billing', 'Priority support']; @override String adRewarded(int minutes) => '+$minutes min added'; @override @@ -547,8 +517,7 @@ class StringsEn extends AppText { @override String autoConnectingTo(String name) => 'Auto-connecting to $name…'; @override - String updateAvailableTitle(String version) => - 'New version v$version available'; + String updateAvailableTitle(String version) => 'New version v$version available'; @override String codeSentTo(String email) => 'Code sent to $email'; } diff --git a/client/lib/l10n/strings_es.dart b/client/lib/l10n/strings_es.dart index 10f72ae..29c7bf6 100644 --- a/client/lib/l10n/strings_es.dart +++ b/client/lib/l10n/strings_es.dart @@ -61,8 +61,7 @@ class StringsEs extends AppText { @override String get watchAdMore => 'Mira un anuncio para más tiempo'; @override - String get quotaExhaustedNotice => - 'Tiempo gratis diario agotado. Mejora tu plan para seguir conectado.'; + String get quotaExhaustedNotice => 'Tiempo gratis diario agotado. Mejora tu plan para seguir conectado.'; @override String get adPlaying => 'Reproduciendo anuncio…'; @override @@ -70,8 +69,7 @@ class StringsEs extends AppText { @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.'; + 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 @@ -81,16 +79,13 @@ class StringsEs extends AppText { @override String get smartSelect => 'Selección inteligente'; @override - String get nodeSwitchBody => - 'Esto desconecta el enlace actual y reconecta a este nodo.'; + 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'; + String get nodeUnhealthySwitched => 'Nodo actual inaccesible: cambiado automáticamente'; @override - String get nodeUnhealthyError => - 'El nodo actual está inaccesible: reconecta o cambia de nodo'; + String get nodeUnhealthyError => 'El nodo actual está inaccesible: reconecta o cambia de nodo'; @override String get nodeReconnecting => 'Red inestable, reconectando…'; @override @@ -148,8 +143,7 @@ class StringsEs extends AppText { @override String get deviceLimitTitle => 'Límite de dispositivos alcanzado'; @override - String get deviceLimitDesc => - 'Tu plan permite hasta %s dispositivos. Elimina uno para continuar.'; + String get deviceLimitDesc => 'Tu plan permite hasta %s dispositivos. Elimina uno para continuar.'; @override String get deviceLimitRemoveOldest => 'Eliminar el menos usado recientemente'; @override @@ -179,18 +173,15 @@ class StringsEs extends AppText { @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.'; + 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.'; + 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.'; + 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 @@ -232,8 +223,7 @@ class StringsEs extends AppText { @override String get webUserCenter => 'Centro de usuario (Web)'; @override - String get updateNotesFallback => - 'Esta actualización incluye correcciones y mejoras de estabilidad.'; + String get updateNotesFallback => 'Esta actualización incluye correcciones y mejoras de estabilidad.'; @override String get updateLater => 'Más tarde'; @override @@ -241,8 +231,7 @@ class StringsEs extends AppText { @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'; + String get updateCheckFailed => 'Falló la búsqueda de actualizaciones, inténtalo más tarde'; @override String get freePlan => 'Gratis'; @override @@ -272,8 +261,7 @@ class StringsEs extends AppText { @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:'; + String get buySub => 'El pago dentro de la app no está disponible. Consigue un código mediante:'; @override String get chStore => 'Tienda autoservicio'; @override @@ -281,8 +269,7 @@ class StringsEs extends AppText { @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.'; + 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 @@ -324,8 +311,7 @@ class StringsEs extends AppText { @override String get forgotPw => '¿Olvidaste la contraseña?'; @override - String get tos => - 'Al continuar aceptas nuestros Términos y Política de privacidad'; + String get tos => 'Al continuar aceptas nuestros Términos y Política de privacidad'; @override String get obSkip => 'Omitir'; @override @@ -337,18 +323,15 @@ class StringsEs extends AppText { @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.'; + 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.'; + 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.'; + String get ob3Sub => 'Cifrado de extremo a extremo. No guardamos ningún registro de navegación.'; @override String get purchaseTitle => 'Comprar plan'; @override @@ -378,18 +361,15 @@ class StringsEs extends AppText { @override String get payNetworkLabel => 'Red'; @override - String get payExactAmountHint => - 'Envía el importe exacto; se activa automáticamente'; + String get payExactAmountHint => 'Envía el importe exacto; se activa automáticamente'; @override String get copied => 'Copiado'; @override String get openAlipay => 'Pagar con Alipay'; @override - String get openAlipayHint => - 'Vuelve tras pagar; esta página se actualiza automáticamente'; + String get openAlipayHint => 'Vuelve tras pagar; esta página se actualiza automáticamente'; @override - String get openAlipayFailed => - 'No se pudo abrir Alipay; comprueba que esté instalado o inténtalo de nuevo'; + String get openAlipayFailed => 'No se pudo abrir Alipay; comprueba que esté instalado o inténtalo de nuevo'; @override String get awaitingPayment => 'Esperando el pago'; @override @@ -409,11 +389,9 @@ class StringsEs extends AppText { @override String get qrNotSupported => 'Copia el contenido y ábrelo en Alipay'; @override - String get orderCreateFailed => - 'Error al crear el pedido, inténtalo de nuevo más tarde'; + String get orderCreateFailed => 'Error al crear el pedido, inténtalo de nuevo más tarde'; @override - String get payUnsupportedRenderType => - 'No compatible, cambia el método de pago'; + String get payUnsupportedRenderType => 'No compatible, cambia el método de pago'; @override String get payNoActiveOrder => 'No hay pedidos activos'; @override @@ -473,8 +451,7 @@ class StringsEs extends AppText { @override String get inviteRewardRegister => 'Registro de amigo · +3 días cada uno'; @override - String get inviteRewardFirstBuy => - 'Primera compra del amigo · +7 días cada uno'; + String get inviteRewardFirstBuy => 'Primera compra del amigo · +7 días cada uno'; @override String get inviteRewardJoinTg => 'Unirse al canal de TG · +3 días'; @override @@ -528,26 +505,11 @@ class StringsEs extends AppText { @override List get days7 => const ['L', 'M', 'X', 'J', 'V', 'S', 'D']; @override - List 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', - ]; + List 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 get featsPro => const [ - 'Más de 80 ubicaciones', - 'Ilimitado · rápido', - '5 dispositivos', - 'Optimizado para streaming', - ]; + List get featsPro => const ['Más de 80 ubicaciones', 'Ilimitado · rápido', '5 dispositivos', 'Optimizado para streaming']; @override - List get featsTeam => const [ - 'Todo lo de Pro', - '10 plazas', - 'Facturación centralizada', - 'Soporte prioritario', - ]; + List get featsTeam => const ['Todo lo de Pro', '10 plazas', 'Facturación centralizada', 'Soporte prioritario']; @override String adRewarded(int minutes) => '+$minutes min añadidos'; @override @@ -555,8 +517,7 @@ class StringsEs extends AppText { @override String autoConnectingTo(String name) => 'Conectando automáticamente a $name…'; @override - String updateAvailableTitle(String version) => - 'Nueva versión v$version disponible'; + String updateAvailableTitle(String version) => 'Nueva versión v$version disponible'; @override String codeSentTo(String email) => 'Código enviado a $email'; } diff --git a/client/lib/l10n/strings_ja.dart b/client/lib/l10n/strings_ja.dart index 9f48d1f..3d1ff33 100644 --- a/client/lib/l10n/strings_ja.dart +++ b/client/lib/l10n/strings_ja.dart @@ -69,8 +69,7 @@ class StringsJa extends AppText { @override String get quotaDesktopTitle => '本日の無料時間を使い切りました'; @override - String get quotaDesktopBody => - 'モバイルアプリで広告を見ると時間を追加できます。無制限に使うにはアップグレードしてください。'; + String get quotaDesktopBody => 'モバイルアプリで広告を見ると時間を追加できます。無制限に使うにはアップグレードしてください。'; @override String get gotIt => '了解'; @override @@ -174,18 +173,15 @@ class StringsJa extends AppText { @override String get devSave => '保存'; @override - String get devForceLogoutConfirm => - 'このデバイスのログインセッションを無効にします。オフラインになり、再度サインインが必要になります。デバイスはリストに残ります。'; + String get devForceLogoutConfirm => 'このデバイスのログインセッションを無効にします。オフラインになり、再度サインインが必要になります。デバイスはリストに残ります。'; @override - String get devClearLoginConfirm => - 'このデバイスをリストから完全に削除し、ログインセッションとデータ通信の認証情報を無効にします。再び利用するには、もう一度サインインが必要です。'; + String get devClearLoginConfirm => 'このデバイスをリストから完全に削除し、ログインセッションとデータ通信の認証情報を無効にします。再び利用するには、もう一度サインインが必要です。'; @override String get devCancel => 'キャンセル'; @override String get sessionRevokedTitle => 'このデバイスはサインアウトされました'; @override - String get sessionRevokedBody => - '別のデバイスから、このデバイスがサインアウトされました。もう一度サインインしてください。'; + String get sessionRevokedBody => '別のデバイスから、このデバイスがサインアウトされました。もう一度サインインしてください。'; @override String get sessionRevokedOk => '再度サインイン'; @override @@ -509,26 +505,11 @@ class StringsJa extends AppText { @override List get days7 => const ['月', '火', '水', '木', '金', '土', '日']; @override - List get featsFree => const [ - '基本ノード1つのみ', - '1日10分', - '広告を見て開始', - '登録で7日間無料トライアル', - ]; + List get featsFree => const ['基本ノード1つのみ', '1日10分', '広告を見て開始', '登録で7日間無料トライアル']; @override - List get featsPro => const [ - '80以上の拠点', - '無制限 · 高速', - '5台のデバイス', - 'ストリーミング最適化', - ]; + List get featsPro => const ['80以上の拠点', '無制限 · 高速', '5台のデバイス', 'ストリーミング最適化']; @override - List get featsTeam => const [ - 'Proのすべての機能', - '10ライセンス', - '一括請求', - '優先サポート', - ]; + List get featsTeam => const ['Proのすべての機能', '10ライセンス', '一括請求', '優先サポート']; @override String adRewarded(int minutes) => '+$minutes 分を追加しました'; @override diff --git a/client/lib/l10n/strings_ko.dart b/client/lib/l10n/strings_ko.dart index 1f5eebc..1072f41 100644 --- a/client/lib/l10n/strings_ko.dart +++ b/client/lib/l10n/strings_ko.dart @@ -61,8 +61,7 @@ class StringsKo extends AppText { @override String get watchAdMore => '광고 시청으로 시간 추가'; @override - String get quotaExhaustedNotice => - '오늘 무료 시간을 모두 사용했습니다. 업그레이드하면 계속 연결할 수 있습니다.'; + String get quotaExhaustedNotice => '오늘 무료 시간을 모두 사용했습니다. 업그레이드하면 계속 연결할 수 있습니다.'; @override String get adPlaying => '광고 재생 중…'; @override @@ -70,8 +69,7 @@ class StringsKo extends AppText { @override String get quotaDesktopTitle => '오늘 무료 시간을 모두 사용함'; @override - String get quotaDesktopBody => - '모바일 앱에서 광고를 시청해 시간을 추가하거나, 업그레이드하여 무제한으로 이용하세요.'; + String get quotaDesktopBody => '모바일 앱에서 광고를 시청해 시간을 추가하거나, 업그레이드하여 무제한으로 이용하세요.'; @override String get gotIt => '확인'; @override @@ -175,11 +173,9 @@ class StringsKo extends AppText { @override String get devSave => '저장'; @override - String get devForceLogoutConfirm => - '이 기기의 로그인 세션을 무효화합니다 — 오프라인으로 강제 종료되며 다시 로그인해야 합니다. 기기는 목록에 남아 있습니다.'; + String get devForceLogoutConfirm => '이 기기의 로그인 세션을 무효화합니다 — 오프라인으로 강제 종료되며 다시 로그인해야 합니다. 기기는 목록에 남아 있습니다.'; @override - String get devClearLoginConfirm => - '이 기기를 목록에서 완전히 삭제하고 로그인 세션과 데이터 전송 자격 증명을 무효화합니다. 다시 사용하려면 재로그인해야 합니다.'; + String get devClearLoginConfirm => '이 기기를 목록에서 완전히 삭제하고 로그인 세션과 데이터 전송 자격 증명을 무효화합니다. 다시 사용하려면 재로그인해야 합니다.'; @override String get devCancel => '취소'; @override @@ -509,26 +505,11 @@ class StringsKo extends AppText { @override List get days7 => const ['월', '화', '수', '목', '금', '토', '일']; @override - List get featsFree => const [ - '기본 노드 1개', - '하루 10분', - '광고 시청 후 시작', - '가입 시 7일 무료 체험', - ]; + List get featsFree => const ['기본 노드 1개', '하루 10분', '광고 시청 후 시작', '가입 시 7일 무료 체험']; @override - List get featsPro => const [ - '80개 이상 지역', - '무제한 · 빠른 속도', - '기기 5대', - '스트리밍 최적화', - ]; + List get featsPro => const ['80개 이상 지역', '무제한 · 빠른 속도', '기기 5대', '스트리밍 최적화']; @override - List get featsTeam => const [ - 'Pro의 모든 기능', - '10인 좌석', - '통합 결제', - '우선 지원', - ]; + List get featsTeam => const ['Pro의 모든 기능', '10인 좌석', '통합 결제', '우선 지원']; @override String adRewarded(int minutes) => '+$minutes분 추가됨'; @override diff --git a/client/lib/l10n/strings_ru.dart b/client/lib/l10n/strings_ru.dart index 373c004..b51c674 100644 --- a/client/lib/l10n/strings_ru.dart +++ b/client/lib/l10n/strings_ru.dart @@ -61,8 +61,7 @@ class StringsRu extends AppText { @override String get watchAdMore => 'Смотрите рекламу, чтобы добавить время'; @override - String get quotaExhaustedNotice => - 'Дневной бесплатный лимит исчерпан. Оформите подписку, чтобы продолжить подключение.'; + String get quotaExhaustedNotice => 'Дневной бесплатный лимит исчерпан. Оформите подписку, чтобы продолжить подключение.'; @override String get adPlaying => 'Реклама воспроизводится…'; @override @@ -70,8 +69,7 @@ class StringsRu extends AppText { @override String get quotaDesktopTitle => 'Дневной бесплатный лимит исчерпан'; @override - String get quotaDesktopBody => - 'Смотрите рекламу в мобильном приложении, чтобы добавить время, или оформите подписку для безлимитного доступа.'; + String get quotaDesktopBody => 'Смотрите рекламу в мобильном приложении, чтобы добавить время, или оформите подписку для безлимитного доступа.'; @override String get gotIt => 'Понятно'; @override @@ -81,16 +79,13 @@ class StringsRu extends AppText { @override String get smartSelect => 'Умный выбор'; @override - String get nodeSwitchBody => - 'Текущее соединение будет разорвано и восстановлено через этот узел.'; + String get nodeSwitchBody => 'Текущее соединение будет разорвано и восстановлено через этот узел.'; @override String get nodeSwitchConfirm => 'Переключить'; @override - String get nodeUnhealthySwitched => - 'Текущий узел недоступен — переключение выполнено автоматически'; + String get nodeUnhealthySwitched => 'Текущий узел недоступен — переключение выполнено автоматически'; @override - String get nodeUnhealthyError => - 'Текущий узел недоступен — переподключитесь или смените узел'; + String get nodeUnhealthyError => 'Текущий узел недоступен — переподключитесь или смените узел'; @override String get nodeReconnecting => 'Сеть нестабильна, переподключение…'; @override @@ -148,8 +143,7 @@ class StringsRu extends AppText { @override String get deviceLimitTitle => 'Достигнут лимит устройств'; @override - String get deviceLimitDesc => - 'Ваш тариф допускает до %s устройств. Удалите одно, чтобы продолжить.'; + String get deviceLimitDesc => 'Ваш тариф допускает до %s устройств. Удалите одно, чтобы продолжить.'; @override String get deviceLimitRemoveOldest => 'Удалить давно неиспользуемое'; @override @@ -179,18 +173,15 @@ class StringsRu extends AppText { @override String get devSave => 'Сохранить'; @override - String get devForceLogoutConfirm => - 'Это завершит сеанс входа устройства — оно будет отключено и потребует повторного входа. Устройство останется в списке.'; + String get devForceLogoutConfirm => 'Это завершит сеанс входа устройства — оно будет отключено и потребует повторного входа. Устройство останется в списке.'; @override - String get devClearLoginConfirm => - 'Это полностью удалит устройство из списка и отзовёт его сеанс входа и учётные данные передачи данных. Чтобы снова использовать устройство, нужно войти заново.'; + String get devClearLoginConfirm => 'Это полностью удалит устройство из списка и отзовёт его сеанс входа и учётные данные передачи данных. Чтобы снова использовать устройство, нужно войти заново.'; @override String get devCancel => 'Отмена'; @override String get sessionRevokedTitle => 'Это устройство было отключено'; @override - String get sessionRevokedBody => - 'Вход с этого устройства завершён с другого устройства вашего аккаунта. Пожалуйста, войдите снова.'; + String get sessionRevokedBody => 'Вход с этого устройства завершён с другого устройства вашего аккаунта. Пожалуйста, войдите снова.'; @override String get sessionRevokedOk => 'Войти снова'; @override @@ -222,8 +213,7 @@ class StringsRu extends AppText { @override String get smartRoute => 'Умная маршрутизация'; @override - String get smartRouteSub => - 'Ускорение для зарубежных ресурсов, прямой доступ для локальных'; + String get smartRouteSub => 'Ускорение для зарубежных ресурсов, прямой доступ для локальных'; @override String get killSwitch => 'Kill Switch'; @override @@ -233,8 +223,7 @@ class StringsRu extends AppText { @override String get webUserCenter => 'Личный кабинет (веб)'; @override - String get updateNotesFallback => - 'Это обновление включает исправления и улучшения стабильности.'; + String get updateNotesFallback => 'Это обновление включает исправления и улучшения стабильности.'; @override String get updateLater => 'Позже'; @override @@ -242,8 +231,7 @@ class StringsRu extends AppText { @override String get updateUpToDate => 'У вас установлена последняя версия'; @override - String get updateCheckFailed => - 'Не удалось проверить обновления, попробуйте позже'; + String get updateCheckFailed => 'Не удалось проверить обновления, попробуйте позже'; @override String get freePlan => 'Бесплатный'; @override @@ -273,8 +261,7 @@ class StringsRu extends AppText { @override String get buyTitle => 'Где купить'; @override - String get buySub => - 'Оплата внутри приложения недоступна. Получить код можно через:'; + String get buySub => 'Оплата внутри приложения недоступна. Получить код можно через:'; @override String get chStore => 'Магазин самообслуживания'; @override @@ -282,8 +269,7 @@ class StringsRu extends AppText { @override String get contactTitle => 'Связаться с нами'; @override - String get contactIntro => - 'Нужна помощь? Свяжитесь с нами любым способом ниже — обычно отвечаем за считанные минуты.'; + String get contactIntro => 'Нужна помощь? Свяжитесь с нами любым способом ниже — обычно отвечаем за считанные минуты.'; @override String get contactEmail => 'Поддержка по эл. почте'; @override @@ -325,8 +311,7 @@ class StringsRu extends AppText { @override String get forgotPw => 'Забыли пароль?'; @override - String get tos => - 'Продолжая, вы принимаете наши Условия и Политику конфиденциальности'; + String get tos => 'Продолжая, вы принимаете наши Условия и Политику конфиденциальности'; @override String get obSkip => 'Пропустить'; @override @@ -338,13 +323,11 @@ class StringsRu extends AppText { @override String get ob1Title => 'Подключение по всему миру'; @override - String get ob1Sub => - '80+ локаций, автоматический выбор самого быстрого маршрута, надёжная стабильность.'; + String get ob1Sub => '80+ локаций, автоматический выбор самого быстрого маршрута, надёжная стабильность.'; @override String get ob2Title => 'Разрешите настройку сети'; @override - String get ob2Sub => - 'Система запросит добавление сетевого профиля для создания зашифрованного туннеля.'; + String get ob2Sub => 'Система запросит добавление сетевого профиля для создания зашифрованного туннеля.'; @override String get ob3Title => 'Приватность по умолчанию'; @override @@ -378,18 +361,15 @@ class StringsRu extends AppText { @override String get payNetworkLabel => 'Сеть'; @override - String get payExactAmountHint => - 'Отправьте точную сумму — активация произойдёт автоматически'; + String get payExactAmountHint => 'Отправьте точную сумму — активация произойдёт автоматически'; @override String get copied => 'Скопировано'; @override String get openAlipay => 'Оплатить через Alipay'; @override - String get openAlipayHint => - 'Вернитесь после оплаты — страница обновится автоматически'; + String get openAlipayHint => 'Вернитесь после оплаты — страница обновится автоматически'; @override - String get openAlipayFailed => - 'Не удалось открыть Alipay. Проверьте, установлен ли он, или повторите'; + String get openAlipayFailed => 'Не удалось открыть Alipay. Проверьте, установлен ли он, или повторите'; @override String get awaitingPayment => 'Ожидание оплаты'; @override @@ -409,11 +389,9 @@ class StringsRu extends AppText { @override String get qrNotSupported => 'Скопируйте и откройте в Alipay'; @override - String get orderCreateFailed => - 'Не удалось создать заказ, повторите попытку позже'; + String get orderCreateFailed => 'Не удалось создать заказ, повторите попытку позже'; @override - String get payUnsupportedRenderType => - 'Не поддерживается, смените способ оплаты'; + String get payUnsupportedRenderType => 'Не поддерживается, смените способ оплаты'; @override String get payNoActiveOrder => 'Нет активных заказов'; @override @@ -457,8 +435,7 @@ class StringsRu extends AppText { @override String get inviteEntry => 'Приглашайте друзей и получайте членство'; @override - String get inviteEntrySub => - 'Друг регистрируется · оба получают дни подписки'; + String get inviteEntrySub => 'Друг регистрируется · оба получают дни подписки'; @override String get inviteTitle => 'Пригласить друзей'; @override @@ -528,26 +505,11 @@ class StringsRu extends AppText { @override List get days7 => const ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']; @override - List get featsFree => const [ - 'Только 1 базовый узел', - '10 минут в день', - 'Смотрите рекламу, чтобы начать', - '7 дней бесплатно при регистрации', - ]; + List get featsFree => const ['Только 1 базовый узел', '10 минут в день', 'Смотрите рекламу, чтобы начать', '7 дней бесплатно при регистрации']; @override - List get featsPro => const [ - '80+ локаций', - 'Безлимитно · быстро', - '5 устройств', - 'Оптимизация для стриминга', - ]; + List get featsPro => const ['80+ локаций', 'Безлимитно · быстро', '5 устройств', 'Оптимизация для стриминга']; @override - List get featsTeam => const [ - 'Всё из Pro', - '10 мест', - 'Единый счёт', - 'Приоритетная поддержка', - ]; + List get featsTeam => const ['Всё из Pro', '10 мест', 'Единый счёт', 'Приоритетная поддержка']; @override String adRewarded(int minutes) => '+$minutes мин добавлено'; @override @@ -555,8 +517,7 @@ class StringsRu extends AppText { @override String autoConnectingTo(String name) => 'Автоподключение к $name…'; @override - String updateAvailableTitle(String version) => - 'Доступна новая версия v$version'; + String updateAvailableTitle(String version) => 'Доступна новая версия v$version'; @override String codeSentTo(String email) => 'Код отправлен на $email'; } diff --git a/client/lib/l10n/strings_zh.dart b/client/lib/l10n/strings_zh.dart index 3182f2e..851c752 100644 --- a/client/lib/l10n/strings_zh.dart +++ b/client/lib/l10n/strings_zh.dart @@ -175,8 +175,7 @@ class StringsZh extends AppText { @override String get devForceLogoutConfirm => '将吊销该设备的登录会话,它会被踢下线、需重新登录。设备仍保留在列表中。'; @override - String get devClearLoginConfirm => - '将从设备列表彻底移除此设备,并吊销其登录会话与数据面凭证。该设备需重新登录才能再次使用。'; + String get devClearLoginConfirm => '将从设备列表彻底移除此设备,并吊销其登录会话与数据面凭证。该设备需重新登录才能再次使用。'; @override String get devCancel => '取消'; @override @@ -506,26 +505,11 @@ class StringsZh extends AppText { @override List get days7 => const ['一', '二', '三', '四', '五', '六', '日']; @override - List get featsFree => const [ - '仅 1 个基础节点', - '每日 10 分钟时长', - '使用前观看广告', - '注册享 7 天免费试用', - ]; + List get featsFree => const ['仅 1 个基础节点', '每日 10 分钟时长', '使用前观看广告', '注册享 7 天免费试用']; @override - List get featsPro => const [ - '80+ 全球节点', - '无限流量 · 极速', - '5 台设备同时在线', - '流媒体优化', - ]; + List get featsPro => const ['80+ 全球节点', '无限流量 · 极速', '5 台设备同时在线', '流媒体优化']; @override - List get featsTeam => const [ - '专业版全部功能', - '10 个成员席位', - '集中计费与管理', - '优先客服', - ]; + List get featsTeam => const ['专业版全部功能', '10 个成员席位', '集中计费与管理', '优先客服']; @override String adRewarded(int minutes) => '已加 $minutes 分钟'; @override diff --git a/design/codegen/gen_l10n_dart.mjs b/design/codegen/gen_l10n_dart.mjs index cf35b83..37a1b56 100644 --- a/design/codegen/gen_l10n_dart.mjs +++ b/design/codegen/gen_l10n_dart.mjs @@ -7,12 +7,14 @@ // AppLangLabel / AppLangMisc 逻辑)。Dart 编译器强制 concrete⊆abstract 兜底漂移。 // // 用法:node design/codegen/gen_l10n_dart.mjs -// 生成后自动 `dart format`,保证 CI「重生成零 diff」闸稳定(漂移闸 = 重生成 + git diff)。 +// 输出为确定性终态格式,不跑 dart format(其结果依赖包解析状态、跨环境不稳, +// 会让漂移闸误报)。漂移闸 = 纯 node 重生成 + git diff,无 dart 依赖。 +// analyze 不校验格式,故手控格式即可;若单独启用 `dart format --set-exit-if-changed`, +// 需让本文件输出与之对齐(当前未启用)。 import fs from 'node:fs'; import path from 'node:path'; import url from 'node:url'; -import { execFileSync } from 'node:child_process'; const ROOT = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..', '..'); const SRC = path.join(ROOT, 'design/i18n/strings.json'); @@ -72,12 +74,5 @@ for (const lang of LANGS) { fs.writeFileSync(out, genFile(lang)); written.push(out); } - -// dart format 定型,保证零 diff 闸稳定 -try { - execFileSync('dart', ['format', ...written], { cwd: ROOT, stdio: 'inherit' }); -} catch (e) { - console.warn('⚠ dart format 失败(dart 不在 PATH?),已生成未格式化文件:', e.message); -} const g = Object.keys(getters).length, l = Object.keys(lists).length, me = Object.keys(methods).length; console.log(`✓ 生成 ${written.length} 份:getters=${g} lists=${l} methods=${me} × ${LANGS.length} 语`);