chore: Improve translation script and translate vi, es, et (#3555)
* improve translation script * update translation script, more translates for es, et, vi
This commit is contained in:
parent
c1c4152633
commit
5d28cea789
5 changed files with 9592 additions and 39 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2025-05-23 14:51:23.780564",
|
||||
"@@last_modified": "2025-07-24 13:19:34.706481",
|
||||
"about": "Acerca de",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -5917,5 +5917,416 @@
|
|||
"@announcements": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"customReaction": "Reacción personalizada",
|
||||
"chatCapacitySetTooLow": "La capacidad de chat debe ser al menos {count}.",
|
||||
"spaceCapacitySetTooLow": "La capacidad del espacio debe ser al menos {count}.",
|
||||
"grammarCopyVERBFORMshrt": "Corto",
|
||||
"constructUseCollected": "Recopilado en chat",
|
||||
"modeLabel": "Tipo de actividad",
|
||||
"makeYourOwnActivity": "Crea tu propia actividad",
|
||||
"deleteChatDesc": "¿Estás seguro de que deseas eliminar este chat? Se eliminará para todos los participantes y todos los mensajes dentro del chat ya no estarán disponibles para práctica o análisis de aprendizaje.",
|
||||
"deleteSpaceDesc": "El espacio y cualquier chat y/o subespacio seleccionado se eliminarán para todos los participantes y todos los mensajes dentro del chat ya no estarán disponibles para práctica o análisis de aprendizaje. Esta acción no se puede deshacer.",
|
||||
"maxFifty": "Máximo 50",
|
||||
"activities": "Actividades",
|
||||
"access": "Acceso",
|
||||
"addSubspace": "Agregar subespacio",
|
||||
"botSettings": "Configuración del bot",
|
||||
"activitySuggestionTimeoutMessage": "Estamos trabajando arduamente para generar actividades para ti, por favor vuelve en un minuto",
|
||||
"accessSettingsWarning": "¡Ups! Parece que no tienes permiso para configurar las reglas de acceso de esta sala. Debes revisarlas para asegurarte de que sean lo que necesitas y hablar con un administrador de la sala si necesitas cambiarlas",
|
||||
"howSpaceCanBeFound": "Cómo se puede encontrar este espacio",
|
||||
"private": "Privado",
|
||||
"cannotBeFoundInSearch": "No se puede encontrar en la búsqueda",
|
||||
"public": "Público",
|
||||
"visibleToCommunity": "Visible para la comunidad más amplia de Pangea Chat a través de \"Encuentra a tu gente\"",
|
||||
"howSpaceCanBeJoined": "Cómo se puede unirse a este espacio",
|
||||
"canBeFoundVia": "Se puede encontrar a través de:",
|
||||
"canBeFoundViaInvitation": "• invitación",
|
||||
"canBeFoundViaCodeOrLink": "• código o enlace",
|
||||
"canBeFoundViaKnock": "• solicitud para unirse y aprobación del administrador",
|
||||
"createYourSpace": "Crea tu espacio",
|
||||
"youHaveLeveledUp": "¡Has subido de nivel!",
|
||||
"sendActivities": "Enviar actividades",
|
||||
"getStartedBotChatDesc": "¡Chatear con IA es un excelente lugar para comenzar y las herramientas de lectura, escritura, escucha y habla de Pangea facilitan todo!",
|
||||
"getStartedCommunitiesDesc": "¡Aprender con una comunidad es donde brilla Pangea Chat!\nPuedes unirte a tu clase, encontrar una escuela o incluso crear la tuya propia.",
|
||||
"getStartedFriendsDesc": "¿Tienes un amigo que quiere aprender contigo?",
|
||||
"getStartedBotChatComplete": "¡Bien hecho! Estás chateando con el bot!",
|
||||
"getStartedCommunitiesComplete": "¡Genial, te has unido a un espacio!",
|
||||
"getStartedComplete": "¡Has completado esta sección!\nSigue explorando nuestras increíbles funciones chateando con amigos.",
|
||||
"getStartedFriendsComplete": "¡Woohoo! ¡Tienes amigos! 😉",
|
||||
"getStartedBotChatButton": "¡Comienza a chatear!",
|
||||
"getStartedFriendsButton": "Chatear con un amigo",
|
||||
"groupChat": "Chat grupal",
|
||||
"directMessage": "Mensaje directo",
|
||||
"newDirectMessage": "Nuevo mensaje directo",
|
||||
"speakingExercisesTooltip": "Hablar",
|
||||
"noChatsFoundHereYet": "Aún no se han encontrado chats aquí",
|
||||
"duration": "Duración",
|
||||
"transcriptionFailed": "Error al transcribir el audio",
|
||||
"aUserIsKnocking": "Un usuario está solicitando unirse a tu espacio",
|
||||
"usersAreKnocking": "{users} usuarios están solicitando unirse a tu espacio",
|
||||
"failedToFetchTranscription": "Error al obtener la transcripción",
|
||||
"deleteEmptySpaceDesc": "El espacio será eliminado para todos los participantes. Esta acción no se puede deshacer.",
|
||||
"regenerate": "Regenerar",
|
||||
"mySavedActivities": "Mis actividades guardadas",
|
||||
"noSavedActivities": "No hay actividades guardadas",
|
||||
"saveActivity": "Guardar esta actividad",
|
||||
"yourSavedActivities": "Actividades guardadas",
|
||||
"failedToPlayVideo": "Error al reproducir el video",
|
||||
"done": "Hecho",
|
||||
"inThisSpace": "En este espacio",
|
||||
"myContacts": "Mis contactos",
|
||||
"inviteAllInSpace": "Invitar a todos en este espacio",
|
||||
"spaceParticipantsHaveBeenInvitedToTheChat": "Todos los participantes del espacio han sido invitados al chat",
|
||||
"numKnocking": "{count} tocando",
|
||||
"numInvited": "{count} invitado",
|
||||
"saved": "Guardado",
|
||||
"reset": "Restablecer",
|
||||
"errorGenerateActivityMessage": "Error al generar la actividad",
|
||||
"errorRegenerateActivityMessage": "Error al regenerar la actividad",
|
||||
"errorFetchingActivitiesMessage": "Error al obtener actividades",
|
||||
"errorFetchingDefinition": "Error al obtener la definición",
|
||||
"errorProcessAnalytics": "Error al procesar análisis",
|
||||
"errorDownloading": "Error en la descarga",
|
||||
"errorFetchingLevelSummary": "Error al obtener el resumen del nivel",
|
||||
"errorLoadingSpaceChildren": "Error al cargar chats dentro de este espacio",
|
||||
"unexpectedError": "Error inesperado.",
|
||||
"pleaseReload": "Por favor, recarga e intenta de nuevo.",
|
||||
"translationError": "Error de traducción",
|
||||
"errorFetchingTranslation": "Error al obtener la traducción",
|
||||
"errorFetchingActivity": "Error al obtener la actividad",
|
||||
"check": "Verificar",
|
||||
"unableToFindRoom": "No se encontró chat o espacio con ese código. Por favor, intenta de nuevo.",
|
||||
"@customReaction": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@chatCapacitySetTooLow": {
|
||||
"type": "int",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@spaceCapacitySetTooLow": {
|
||||
"type": "int",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@grammarCopyVERBFORMshrt": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@constructUseCollected": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@modeLabel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@deleteChatDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@deleteSpaceDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@maxFifty": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@activities": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@access": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@addSubspace": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@botSettings": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@activitySuggestionTimeoutMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@accessSettingsWarning": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@howSpaceCanBeFound": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@private": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@cannotBeFoundInSearch": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@public": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@visibleToCommunity": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@howSpaceCanBeJoined": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@canBeFoundVia": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@canBeFoundViaInvitation": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@canBeFoundViaCodeOrLink": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@canBeFoundViaKnock": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@createYourSpace": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@youHaveLeveledUp": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@sendActivities": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedBotChatDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedCommunitiesDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedFriendsDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedBotChatComplete": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedCommunitiesComplete": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedComplete": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedFriendsComplete": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedBotChatButton": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@getStartedFriendsButton": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@groupChat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@directMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@newDirectMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@speakingExercisesTooltip": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@noChatsFoundHereYet": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@duration": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@transcriptionFailed": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@aUserIsKnocking": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@usersAreKnocking": {
|
||||
"type": "int",
|
||||
"placeholders": {
|
||||
"users": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@failedToFetchTranscription": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@deleteEmptySpaceDesc": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@regenerate": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@mySavedActivities": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@noSavedActivities": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@saveActivity": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@yourSavedActivities": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@failedToPlayVideo": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@done": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@inThisSpace": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@myContacts": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@inviteAllInSpace": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@spaceParticipantsHaveBeenInvitedToTheChat": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@numKnocking": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@numInvited": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@saved": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@reset": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorGenerateActivityMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorRegenerateActivityMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingActivitiesMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingDefinition": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorProcessAnalytics": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorDownloading": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingLevelSummary": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorLoadingSpaceChildren": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@unexpectedError": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@pleaseReload": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@translationError": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingTranslation": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingActivity": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@check": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@unableToFindRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
7744
lib/l10n/intl_et.arb
7744
lib/l10n/intl_et.arb
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"@@last_modified": "2025-07-10 15:55:50.516947",
|
||||
"@@last_modified": "2025-07-24 12:57:05.290437",
|
||||
"about": "Giới thiệu",
|
||||
"@about": {
|
||||
"type": "String",
|
||||
|
|
@ -4501,5 +4501,103 @@
|
|||
"@saved": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"chatCapacitySetTooLow": "Dung lượng trò chuyện phải ít nhất là {count}.",
|
||||
"spaceCapacitySetTooLow": "Dung lượng không gian phải ít nhất là {count}.",
|
||||
"reset": "Đặt lại",
|
||||
"errorGenerateActivityMessage": "Không thể tạo hoạt động",
|
||||
"errorRegenerateActivityMessage": "Không thể tái tạo hoạt động",
|
||||
"errorFetchingActivitiesMessage": "Không thể lấy hoạt động",
|
||||
"errorFetchingDefinition": "Không thể lấy định nghĩa",
|
||||
"errorProcessAnalytics": "Không thể xử lý phân tích",
|
||||
"errorDownloading": "Tải xuống thất bại",
|
||||
"errorFetchingLevelSummary": "Không thể lấy tóm tắt cấp độ",
|
||||
"errorLoadingSpaceChildren": "Không thể tải trò chuyện trong không gian này",
|
||||
"unexpectedError": "Lỗi không mong đợi.",
|
||||
"pleaseReload": "Vui lòng tải lại và thử lại.",
|
||||
"translationError": "Lỗi dịch thuật",
|
||||
"errorFetchingTranslation": "Không thể lấy bản dịch",
|
||||
"errorFetchingActivity": "Không thể lấy hoạt động",
|
||||
"check": "Kiểm tra",
|
||||
"unableToFindRoom": "Không tìm thấy trò chuyện hoặc không gian nào với mã đó. Vui lòng thử lại.",
|
||||
"@chatCapacitySetTooLow": {
|
||||
"type": "int",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@spaceCapacitySetTooLow": {
|
||||
"type": "int",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@reset": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorGenerateActivityMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorRegenerateActivityMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingActivitiesMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingDefinition": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorProcessAnalytics": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorDownloading": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingLevelSummary": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorLoadingSpaceChildren": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@unexpectedError": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@pleaseReload": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@translationError": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingTranslation": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@errorFetchingActivity": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@check": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@unableToFindRoom": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
1196
scripts/languages.json
Normal file
1196
scripts/languages.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,23 +1,62 @@
|
|||
"""
|
||||
Prerequiresite:
|
||||
- Ensure you have an up-to-date `needed-translations.txt` file should you wish to translate only the missing translation keys. To generate an updated `needed-translations.txt` file, run `flutter gen-l10n`
|
||||
- Ensure you have python `openai` package installed. If not, run `pip install openai`.
|
||||
- Ensure you have an OpenAI API key set in your environment variable `OPENAI_API_KEY`. If not, you can set it by running `export OPENAI_API_KEY=your-api-key` on MacOS/Linux.
|
||||
- Ensure you have an up-to-date `needed-translations.txt` file should you wish to translate only the missing translation keys. To generate an updated `needed-translations.txt` file, run:
|
||||
```
|
||||
flutter gen-l10n
|
||||
```
|
||||
|
||||
- Ensure you have python `openai` package installed. If not, run:
|
||||
```
|
||||
pip install openai
|
||||
```
|
||||
|
||||
- Ensure you have an OpenAI API key set in your environment variable `OPENAI_API_KEY`. If not, you can set it by running:
|
||||
```
|
||||
export OPENAI_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
- Ensure vi language translations are up-to-date. This script uses en->vi translations as an example on how to translate so it is necessary. If not, you can run:
|
||||
```
|
||||
python scripts/translate.py --lang vi --lang-display-name "Vietnamese" --mode append
|
||||
```
|
||||
|
||||
3 modes:
|
||||
- append mode (default): translate only the missing translation keys
|
||||
- upsert mode (not implemented): translate everything (all keys from English)
|
||||
- update mode (not implemented): specify keys to translate and update their metadata
|
||||
|
||||
Usage:
|
||||
python scripts/translate.py
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import random
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
l10n_dir = Path(__file__).parent.parent / "lib" / "l10n"
|
||||
|
||||
|
||||
def load_needed_translations() -> dict[str, list[str]]:
|
||||
import json
|
||||
from pathlib import Path
|
||||
def load_all_keys() -> list[str]:
|
||||
"""
|
||||
Load all translation keys from intl_en.arb file.
|
||||
"""
|
||||
path_to_en_translations = l10n_dir / "intl_en.arb"
|
||||
if not path_to_en_translations.exists():
|
||||
raise FileNotFoundError(
|
||||
f"File not found: {path_to_en_translations}. Please run `flutter gen-l10n` to generate the file."
|
||||
)
|
||||
with open(path_to_en_translations, encoding="utf-8") as f:
|
||||
translations = json.loads(f.read())
|
||||
return [key for key in translations.keys() if not key.startswith("@")]
|
||||
|
||||
|
||||
def load_needed_translations() -> dict[str, list[str]]:
|
||||
path_to_needed_translations = (
|
||||
Path(__file__).parent.parent / "needed-translations.txt"
|
||||
)
|
||||
|
|
@ -25,31 +64,30 @@ def load_needed_translations() -> dict[str, list[str]]:
|
|||
raise FileNotFoundError(
|
||||
f"File not found: {path_to_needed_translations}. Please run `flutter gen-l10n` to generate the file."
|
||||
)
|
||||
with open(path_to_needed_translations) as f:
|
||||
with open(path_to_needed_translations, encoding="utf-8") as f:
|
||||
needed_translations = json.loads(f.read())
|
||||
|
||||
supported_langs = load_supported_languages()
|
||||
all_keys = load_all_keys()
|
||||
for lang_code, _ in supported_langs:
|
||||
if lang_code not in needed_translations:
|
||||
needed_translations[lang_code] = all_keys
|
||||
|
||||
return needed_translations
|
||||
|
||||
|
||||
def load_translations(lang_code: str) -> dict[str, str]:
|
||||
import json
|
||||
|
||||
path_to_translations = l10n_dir / f"intl_{lang_code}.arb"
|
||||
if not path_to_translations.exists():
|
||||
raise FileNotFoundError(
|
||||
f"File not found: {path_to_translations}. Please run `flutter gen-l10n` to generate the file."
|
||||
)
|
||||
|
||||
with open(path_to_translations) as f:
|
||||
translations = json.loads(f.read())
|
||||
translations = {}
|
||||
else:
|
||||
with open(path_to_translations, encoding="utf-8") as f:
|
||||
translations = json.loads(f.read())
|
||||
|
||||
return translations
|
||||
|
||||
|
||||
def save_translations(lang_code: str, translations: dict[str, str]) -> None:
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
|
||||
path_to_translations = l10n_dir / f"intl_{lang_code}.arb"
|
||||
|
||||
|
|
@ -58,7 +96,7 @@ def save_translations(lang_code: str, translations: dict[str, str]) -> None:
|
|||
|
||||
# Load existing data to preserve order if exists.
|
||||
if path_to_translations.exists():
|
||||
with open(path_to_translations, "r") as f:
|
||||
with open(path_to_translations, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
existing_data = json.load(f, object_pairs_hook=OrderedDict)
|
||||
except json.JSONDecodeError:
|
||||
|
|
@ -73,7 +111,7 @@ def save_translations(lang_code: str, translations: dict[str, str]) -> None:
|
|||
else:
|
||||
existing_data[key] = value # new key appended at the end
|
||||
|
||||
with open(path_to_translations, "w") as f:
|
||||
with open(path_to_translations, "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(existing_data, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
|
|
@ -173,14 +211,10 @@ def reconcile_metadata(
|
|||
save_translations(lang_code, translations)
|
||||
|
||||
|
||||
def translate(lang_code: str, lang_display_name: str) -> None:
|
||||
def append_translate(lang_code: str, lang_display_name: str) -> None:
|
||||
"""
|
||||
Translate the needed translations from English to the target language.
|
||||
"""
|
||||
import json
|
||||
import random
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
needed_translations = load_needed_translations()
|
||||
needed_translations = needed_translations.get(lang_code, [])
|
||||
|
|
@ -274,7 +308,7 @@ def translate(lang_code: str, lang_display_name: str) -> None:
|
|||
"content": prompt,
|
||||
},
|
||||
],
|
||||
model="gpt-4o-mini",
|
||||
model="gpt-4.1-nano",
|
||||
temperature=0.0,
|
||||
)
|
||||
response = chat_completion.choices[0].message.content
|
||||
|
|
@ -292,15 +326,91 @@ def translate(lang_code: str, lang_display_name: str) -> None:
|
|||
reconcile_metadata(lang_code, needed_translations, english_translations_dict)
|
||||
|
||||
|
||||
"""Example usage:
|
||||
python scripts/translate.py
|
||||
def load_supported_languages() -> list[tuple[str, str]]:
|
||||
"""
|
||||
Load the supported languages from the languages.json file.
|
||||
"""
|
||||
with open("scripts/languages.json", "r", encoding="utf-8") as f:
|
||||
raw_languages = json.load(f)
|
||||
languages: list[tuple[str, str]] = []
|
||||
for lang in raw_languages:
|
||||
assert isinstance(lang, dict), "Each language entry must be a dictionary."
|
||||
language_code = lang.get("language_code", None)
|
||||
language_name = lang.get("language_name", None)
|
||||
assert (
|
||||
language_code and language_name
|
||||
), f"Each language must have a 'language_code' and 'language_name'. Found: {lang}"
|
||||
languages.append((language_code, language_name))
|
||||
return languages
|
||||
|
||||
|
||||
"""
|
||||
python scripts/translate.py --lang vi --lang-display-name "Vietnamese" --mode append
|
||||
|
||||
python scripts/translate.py --translate-all --mode append
|
||||
"""
|
||||
if __name__ == "__main__":
|
||||
lang_code = input("Enter the language code (e.g. vi, en): ").strip()
|
||||
lang_display_name = input(
|
||||
"Enter the language display name (e.g. Vietnamese, English): "
|
||||
parser = argparse.ArgumentParser(description="Translate app strings.")
|
||||
|
||||
parser.add_argument(
|
||||
"--lang",
|
||||
type=str,
|
||||
help="Language code to translate to (e.g. 'vi' for Vietnamese, 'en' for English).",
|
||||
)
|
||||
translate(
|
||||
lang_code=lang_code,
|
||||
lang_display_name=lang_display_name,
|
||||
|
||||
parser.add_argument(
|
||||
"--lang-display-name",
|
||||
type=str,
|
||||
help="Display name of the language (e.g. 'Vietnamese', 'English').",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
type=str,
|
||||
choices=["append", "upsert", "update"],
|
||||
default="append",
|
||||
help="Mode of translation: 'append' to translate only missing keys, 'upsert' to translate all keys, 'update' to specify keys to translate and update their metadata.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--translate-all",
|
||||
action="store_true",
|
||||
help="Translate all keys (overrides the mode).",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
translate_all = args.translate_all
|
||||
|
||||
lang_code = args.lang
|
||||
|
||||
lang_display_name = args.lang_display_name
|
||||
|
||||
mode = args.mode
|
||||
|
||||
if not translate_all:
|
||||
assert (
|
||||
args.lang
|
||||
), "Language code is required if translate all is not set. Use --lang to specify the language code."
|
||||
assert (
|
||||
args.lang_display_name
|
||||
), "Language display name is required if translate all is not set. Use --lang-display-name to specify the language display name."
|
||||
|
||||
if mode == "append":
|
||||
if not translate_all:
|
||||
append_translate(
|
||||
lang_code=lang_code,
|
||||
lang_display_name=lang_display_name,
|
||||
)
|
||||
else:
|
||||
languages = load_supported_languages()
|
||||
for i, (lang_code, lang_display_name) in enumerate(languages):
|
||||
print(f"Translating {i + 1}/{len(languages)}: {lang_display_name}")
|
||||
append_translate(
|
||||
lang_code=lang_code,
|
||||
lang_display_name=lang_display_name,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Mode '{mode}' is not implemented yet. Please use 'append' mode for now."
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue