262 lines
13 KiB
Python
Executable File
262 lines
13 KiB
Python
Executable File
import os
|
|
import json
|
|
import glob
|
|
import copy
|
|
|
|
APP_PATH = r'c:\Dockers\dockersyscms\app'
|
|
MODULES_PATH = os.path.join(APP_PATH, 'modules')
|
|
|
|
# Simple dictionary for common terms to attempt basic translation
|
|
COMMON_TRANSLATIONS = {
|
|
'pt_BR': {
|
|
'save': 'Salvar', 'cancel': 'Cancelar', 'edit': 'Editar', 'delete': 'Excluir',
|
|
'name': 'Nome', 'description': 'Descrição', 'title': 'Título', 'actions': 'Ações',
|
|
'create': 'Criar', 'update': 'Atualizar', 'back': 'Voltar', 'view': 'Visualizar',
|
|
'home': 'Início', 'dashboard': 'Painel', 'login': 'Entrar', 'register': 'Cadastrar',
|
|
'email': 'E-mail', 'password': 'Senha', 'logout': 'Sair', 'profile': 'Perfil',
|
|
'search': 'Buscar', 'filter': 'Filtrar', 'reset': 'Limpar', 'status': 'Status',
|
|
'active': 'Ativo', 'inactive': 'Inativo', 'yes': 'Sim', 'no': 'Não',
|
|
'success': 'Sucesso', 'error': 'Erro', 'warning': 'Aviso', 'info': 'Info',
|
|
'confirm': 'Confirmar', 'close': 'Fechar', 'image': 'Imagem', 'file': 'Arquivo',
|
|
'date': 'Data', 'author': 'Autor', 'category': 'Categoria', 'categories': 'Categorias',
|
|
'post': 'Post', 'posts': 'Posts', 'comment': 'Comentário', 'comments': 'Comentários',
|
|
'user': 'Usuário', 'users': 'Usuários', 'role': 'Função', 'roles': 'Funções',
|
|
'permission': 'Permissão', 'permissions': 'Permissões', 'settings': 'Configurações'
|
|
},
|
|
'en_US': {
|
|
'save': 'Save', 'cancel': 'Cancel', 'edit': 'Edit', 'delete': 'Delete',
|
|
'name': 'Name', 'description': 'Description', 'title': 'Title', 'actions': 'Actions',
|
|
'create': 'Create', 'update': 'Update', 'back': 'Back', 'view': 'View',
|
|
'home': 'Home', 'dashboard': 'Dashboard', 'login': 'Login', 'register': 'Register',
|
|
'email': 'Email', 'password': 'Password', 'logout': 'Logout', 'profile': 'Profile',
|
|
'search': 'Search', 'filter': 'Filter', 'reset': 'Reset', 'status': 'Status',
|
|
'active': 'Active', 'inactive': 'Inactive', 'yes': 'Yes', 'no': 'No',
|
|
'success': 'Success', 'error': 'Error', 'warning': 'Warning', 'info': 'Info',
|
|
'confirm': 'Confirm', 'close': 'Close', 'image': 'Image', 'file': 'File',
|
|
'date': 'Date', 'author': 'Author', 'category': 'Category', 'categories': 'Categories',
|
|
'post': 'Post', 'posts': 'Posts', 'comment': 'Comment', 'comments': 'Comments',
|
|
'user': 'User', 'users': 'Users', 'role': 'Role', 'roles': 'Roles',
|
|
'permission': 'Permission', 'permissions': 'Permissions', 'settings': 'Settings'
|
|
},
|
|
'es_ES': {
|
|
'save': 'Guardar', 'cancel': 'Cancelar', 'edit': 'Editar', 'delete': 'Eliminar',
|
|
'name': 'Nombre', 'description': 'Descripción', 'title': 'Título', 'actions': 'Acciones',
|
|
'create': 'Crear', 'update': 'Actualizar', 'back': 'Volver', 'view': 'Ver',
|
|
'home': 'Inicio', 'dashboard': 'Tablero', 'login': 'Iniciar sesión', 'register': 'Registrarse',
|
|
'email': 'Correo', 'password': 'Contraseña', 'logout': 'Cerrar sesión', 'profile': 'Perfil',
|
|
'search': 'Buscar', 'filter': 'Filtrar', 'reset': 'Restablecer', 'status': 'Estado',
|
|
'active': 'Activo', 'inactive': 'Inactivo', 'yes': 'Sí', 'no': 'No',
|
|
'success': 'Éxito', 'error': 'Error', 'warning': 'Advertencia', 'info': 'Info',
|
|
'confirm': 'Confirmar', 'close': 'Cerrar', 'image': 'Imagen', 'file': 'Archivo',
|
|
'date': 'Fecha', 'author': 'Autor', 'category': 'Categoría', 'categories': 'Categorías',
|
|
'post': 'Publicación', 'posts': 'Publicaciones', 'comment': 'Comentario', 'comments': 'Comentarios',
|
|
'user': 'Usuario', 'users': 'Usuarios', 'role': 'Rol', 'roles': 'Roles',
|
|
'permission': 'Permiso', 'permissions': 'Permisos', 'settings': 'Configuraciones'
|
|
},
|
|
'de_DE': {
|
|
'save': 'Speichern', 'cancel': 'Abbrechen', 'edit': 'Bearbeiten', 'delete': 'Löschen',
|
|
'name': 'Name', 'description': 'Beschreibung', 'title': 'Titel', 'actions': 'Aktionen',
|
|
'create': 'Erstellen', 'update': 'Aktualisieren', 'back': 'Zurück', 'view': 'Ansehen',
|
|
'home': 'Startseite', 'dashboard': 'Dashboard', 'login': 'Anmelden', 'register': 'Registrieren',
|
|
'email': 'E-Mail', 'password': 'Passwort', 'logout': 'Abmelden', 'profile': 'Profil',
|
|
'search': 'Suchen', 'filter': 'Filtern', 'reset': 'Zurücksetzen', 'status': 'Status',
|
|
'active': 'Aktiv', 'inactive': 'Inaktiv', 'yes': 'Ja', 'no': 'Nein',
|
|
'success': 'Erfolg', 'error': 'Fehler', 'warning': 'Warnung', 'info': 'Info',
|
|
'confirm': 'Bestätigen', 'close': 'Schließen', 'image': 'Bild', 'file': 'Datei',
|
|
'date': 'Datum', 'author': 'Autor', 'category': 'Kategorie', 'categories': 'Kategorien',
|
|
'post': 'Beitrag', 'posts': 'Beiträge', 'comment': 'Kommentar', 'comments': 'Kommentare',
|
|
'user': 'Benutzer', 'users': 'Benutzer', 'role': 'Rolle', 'roles': 'Rollen',
|
|
'permission': 'Berechtigung', 'permissions': 'Berechtigungen', 'settings': 'Einstellungen'
|
|
},
|
|
'fr_FR': {
|
|
'save': 'Enregistrer', 'cancel': 'Annuler', 'edit': 'Modifier', 'delete': 'Supprimer',
|
|
'name': 'Nom', 'description': 'Description', 'title': 'Titre', 'actions': 'Actions',
|
|
'create': 'Créer', 'update': 'Mettre à jour', 'back': 'Retour', 'view': 'Voir',
|
|
'home': 'Accueil', 'dashboard': 'Tableau de bord', 'login': 'Connexion', 'register': 'S\'inscrire',
|
|
'email': 'E-mail', 'password': 'Mot de passe', 'logout': 'Déconnexion', 'profile': 'Profil',
|
|
'search': 'Rechercher', 'filter': 'Filtrer', 'reset': 'Réinitialiser', 'status': 'Statut',
|
|
'active': 'Actif', 'inactive': 'Inactif', 'yes': 'Oui', 'no': 'Non',
|
|
'success': 'Succès', 'error': 'Erreur', 'warning': 'Attention', 'info': 'Info',
|
|
'confirm': 'Confirmer', 'close': 'Fermer', 'image': 'Image', 'file': 'Fichier',
|
|
'date': 'Date', 'author': 'Auteur', 'category': 'Catégorie', 'categories': 'Catégories',
|
|
'post': 'Article', 'posts': 'Articles', 'comment': 'Commentaire', 'comments': 'Commentaires',
|
|
'user': 'Utilisateur', 'users': 'Utilisateurs', 'role': 'Rôle', 'roles': 'Rôles',
|
|
'permission': 'Permission', 'permissions': 'Permissions', 'settings': 'Paramètres'
|
|
}
|
|
}
|
|
|
|
def get_module_locales():
|
|
locales = {}
|
|
pattern = os.path.join(MODULES_PATH, '*', 'locales', '*', '*.json')
|
|
files = glob.glob(pattern)
|
|
|
|
for file_path in files:
|
|
parts = file_path.split(os.sep)
|
|
try:
|
|
locales_index = parts.index('locales')
|
|
module = parts[locales_index - 1]
|
|
lang = parts[locales_index + 1]
|
|
filename = parts[locales_index + 2]
|
|
|
|
key = f"{module}/{filename}"
|
|
if key not in locales:
|
|
locales[key] = {}
|
|
|
|
locales[key][lang] = file_path
|
|
except ValueError:
|
|
continue
|
|
|
|
return locales
|
|
|
|
def load_json(path):
|
|
try:
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
print(f"Error reading {path}: {e}")
|
|
return {}
|
|
|
|
def save_json(path, data):
|
|
try:
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
|
print(f"Updated: {path}")
|
|
except Exception as e:
|
|
print(f"Error writing {path}: {e}")
|
|
|
|
def flatten_keys(data, prefix=""):
|
|
keys = set()
|
|
if isinstance(data, dict):
|
|
for k, v in data.items():
|
|
full_key = f"{prefix}.{k}" if prefix else k
|
|
keys.add(full_key)
|
|
if isinstance(v, dict):
|
|
keys.update(flatten_keys(v, full_key))
|
|
return keys
|
|
|
|
def get_value_by_key(data, key):
|
|
parts = key.split('.')
|
|
curr = data
|
|
for part in parts:
|
|
if isinstance(curr, dict) and part in curr:
|
|
curr = curr[part]
|
|
else:
|
|
return None
|
|
return curr
|
|
|
|
def set_value_by_key(data, key, value):
|
|
parts = key.split('.')
|
|
curr = data
|
|
for i, part in enumerate(parts[:-1]):
|
|
if part not in curr:
|
|
curr[part] = {}
|
|
elif not isinstance(curr[part], dict):
|
|
# Conflict: trying to use a string as a dict container
|
|
# Convert to dict? No, this structure is invalid if keys conflict.
|
|
# But for filling, we assume structure consistency.
|
|
# If conflict, we skip or overwrite. Here we skip.
|
|
print(f"Warning: Structure conflict for key {key} at {part}")
|
|
return
|
|
curr = curr[part]
|
|
|
|
last_part = parts[-1]
|
|
curr[last_part] = value
|
|
|
|
def try_translate_word(word, target_lang):
|
|
word_lower = word.lower().replace('_', ' ').strip()
|
|
|
|
# Check direct match
|
|
if word_lower in COMMON_TRANSLATIONS.get('en_US', {}):
|
|
# It's an english key?
|
|
# Find the key in en_US
|
|
for k, v in COMMON_TRANSLATIONS['en_US'].items():
|
|
if k == word_lower:
|
|
return COMMON_TRANSLATIONS[target_lang].get(k)
|
|
|
|
# Check if word is in COMMON_TRANSLATIONS for target lang directly (as key)
|
|
if word_lower in COMMON_TRANSLATIONS[target_lang]:
|
|
return COMMON_TRANSLATIONS[target_lang][word_lower]
|
|
|
|
return None
|
|
|
|
def fill_translations():
|
|
groups = get_module_locales()
|
|
|
|
for group_key, langs in groups.items():
|
|
print(f"Processing {group_key}...")
|
|
|
|
# Load all data
|
|
data_by_lang = {lang: load_json(path) for lang, path in langs.items()}
|
|
|
|
# Collect all unique keys
|
|
all_keys = set()
|
|
for data in data_by_lang.values():
|
|
all_keys.update(flatten_keys(data))
|
|
|
|
# Ensure all 5 langs exist in file list (create if missing)
|
|
target_langs = ['en_US', 'pt_BR', 'es_ES', 'de_DE', 'fr_FR']
|
|
module_path = os.path.dirname(os.path.dirname(list(langs.values())[0])) # .../locales
|
|
|
|
for t_lang in target_langs:
|
|
if t_lang not in data_by_lang:
|
|
# Create empty file
|
|
f_path = os.path.join(module_path, t_lang, os.path.basename(list(langs.values())[0]))
|
|
os.makedirs(os.path.dirname(f_path), exist_ok=True)
|
|
data_by_lang[t_lang] = {}
|
|
langs[t_lang] = f_path
|
|
|
|
# Fill missing
|
|
for lang in target_langs:
|
|
data = data_by_lang[lang]
|
|
modified = False
|
|
|
|
for key in all_keys:
|
|
if get_value_by_key(data, key) is None:
|
|
# Find a fallback
|
|
fallback_val = None
|
|
fallback_lang_source = None
|
|
|
|
# 1. Try en_US
|
|
if 'en_US' in data_by_lang:
|
|
val = get_value_by_key(data_by_lang['en_US'], key)
|
|
if val is not None:
|
|
fallback_val = val
|
|
fallback_lang_source = 'en_US'
|
|
|
|
# 2. Try pt_BR
|
|
if fallback_val is None and 'pt_BR' in data_by_lang:
|
|
val = get_value_by_key(data_by_lang['pt_BR'], key)
|
|
if val is not None:
|
|
fallback_val = val
|
|
fallback_lang_source = 'pt_BR'
|
|
|
|
# 3. Any other
|
|
if fallback_val is None:
|
|
for other in data_by_lang:
|
|
val = get_value_by_key(data_by_lang[other], key)
|
|
if val is not None:
|
|
fallback_val = val
|
|
fallback_lang_source = other
|
|
break
|
|
|
|
if fallback_val is not None:
|
|
# Try to smart translate simple keys
|
|
# Extract the last part of the key as a "word" guess
|
|
last_part = key.split('.')[-1]
|
|
translated = try_translate_word(last_part, lang)
|
|
|
|
final_val = translated if translated else fallback_val
|
|
|
|
# If it's a fallback string and we didn't translate it, maybe prepend [LANG] ?
|
|
# User said "complete translations", usually means "don't leave empty".
|
|
# Using English/Portuguese as fallback is standard.
|
|
|
|
set_value_by_key(data, key, final_val)
|
|
modified = True
|
|
|
|
if modified:
|
|
save_json(langs[lang], data)
|
|
|
|
if __name__ == "__main__":
|
|
fill_translations()
|