feat(i18n): ④ l10n 漂移闸 + codegen 改纯 node 确定性输出

- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
This commit is contained in:
wangjia
2026-07-27 08:55:21 +08:00
parent f76e3cdffd
commit 13e0ba6d47
8 changed files with 110 additions and 263 deletions
+27 -66
View File
@@ -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<String> get days7 => const ['L', 'M', 'X', 'J', 'V', 'S', 'D'];
@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',
];
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',
];
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',
];
List<String> 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';
}