mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-06 11:40:52 -06:00
- Replace hardcoded English strings with translation function calls in 36 template files - Update translation files for all supported languages (ar, de, es, fi, fr, he, it, nb, nl, no) - Add over 55,000 new translation entries across all language files - Update extract_translations.py to use 'python -m babel.messages.frontend' instead of pybabel - Improve internationalization coverage for UI elements including: * Skip to content links * Sidebar toggle buttons * Command palette placeholders * Admin dashboard elements * Form labels and buttons * Report templates * Payment and invoice views This commit significantly improves the application's multilingual support by making previously hardcoded strings translatable.
168 lines
7.2 KiB
Python
168 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Script to complete Dutch translations by translating all empty msgstr entries.
|
|
Uses Babel's library for reliable .po file parsing and updates translations.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from babel.messages.pofile import read_po, write_po
|
|
from babel.messages.catalog import Message
|
|
except ImportError:
|
|
print("Error: Babel library not found. Please install it with: pip install Babel")
|
|
sys.exit(1)
|
|
|
|
|
|
def translate_to_dutch(text):
|
|
"""
|
|
Translate English text to Dutch.
|
|
This is a placeholder - in a real scenario, you might use a translation API
|
|
or manual translations. For now, we'll identify what needs translation.
|
|
"""
|
|
# Common translations mapping
|
|
translations = {
|
|
"Your session expired or the page was open too long. Please try again.":
|
|
"Uw sessie is verlopen of de pagina was te lang open. Probeer het opnieuw.",
|
|
"Administrator access required":
|
|
"Beheerdersrechten vereist",
|
|
"Could not update PDF layout due to a database error.":
|
|
"Kon PDF-lay-out niet bijwerken vanwege een databasefout.",
|
|
"PDF layout updated successfully":
|
|
"PDF-lay-out succesvol bijgewerkt",
|
|
"Could not reset PDF layout due to a database error.":
|
|
"Kon PDF-lay-out niet resetten vanwege een databasefout.",
|
|
"PDF layout reset to defaults":
|
|
"PDF-lay-out gereset naar standaardwaarden",
|
|
"Username is required":
|
|
"Gebruikersnaam is vereist",
|
|
"Could not create your account due to a database error. Please try again later.":
|
|
"Kon uw account niet aanmaken vanwege een databasefout. Probeer het later opnieuw.",
|
|
"Welcome! Your account has been created.":
|
|
"Welkom! Uw account is aangemaakt.",
|
|
"User not found. Please contact an administrator.":
|
|
"Gebruiker niet gevonden. Neem contact op met een beheerder.",
|
|
"Could not update your account role due to a database error.":
|
|
"Kon uw accountrol niet bijwerken vanwege een databasefout.",
|
|
"Account is disabled. Please contact an administrator.":
|
|
"Account is uitgeschakeld. Neem contact op met een beheerder.",
|
|
"Welcome back, %(username)s!":
|
|
"Welkom terug, %(username)s!",
|
|
"Unexpected error during login. Please try again or check server logs.":
|
|
"Onverwachte fout tijdens aanmelden. Probeer het opnieuw of controleer de serverlogs.",
|
|
"Goodbye, %(username)s!":
|
|
"Tot ziens, %(username)s!",
|
|
"Invalid avatar file type. Allowed: PNG, JPG, JPEG, GIF, WEBP":
|
|
"Ongeldig avatarbestandstype. Toegestaan: PNG, JPG, JPEG, GIF, WEBP",
|
|
"Invalid image file.":
|
|
"Ongeldig afbeeldingsbestand.",
|
|
"Failed to save avatar on server.":
|
|
"Kon avatar niet opslaan op server.",
|
|
"Profile updated successfully":
|
|
"Profiel succesvol bijgewerkt",
|
|
"Could not update your profile due to a database error.":
|
|
"Kon uw profiel niet bijwerken vanwege een databasefout.",
|
|
"Avatar removed":
|
|
"Avatar verwijderd",
|
|
"Failed to remove avatar.":
|
|
"Kon avatar niet verwijderen.",
|
|
"Single Sign-On is not configured yet. Please contact an administrator.":
|
|
"Single Sign-On is nog niet geconfigureerd. Neem contact op met een beheerder.",
|
|
"Single Sign-On is not configured.":
|
|
"Single Sign-On is niet geconfigureerd.",
|
|
"Authentication failed: missing issuer or subject claim. Please check OIDC configuration.":
|
|
"Authenticatie mislukt: ontbrekende issuer of subject claim. Controleer de OIDC-configuratie.",
|
|
"User account does not exist and self-registration is disabled.":
|
|
"Gebruikersaccount bestaat niet en zelfregistratie is uitgeschakeld.",
|
|
"Could not create your account due to a database error.":
|
|
"Kon uw account niet aanmaken vanwege een databasefout.",
|
|
"Unexpected error during SSO login. Please try again or contact support.":
|
|
"Onverwachte fout tijdens SSO-aanmelden. Probeer het opnieuw of neem contact op met ondersteuning.",
|
|
"Event created successfully":
|
|
"Gebeurtenis succesvol aangemaakt",
|
|
"Event updated successfully":
|
|
"Gebeurtenis succesvol bijgewerkt",
|
|
}
|
|
|
|
return translations.get(text, "")
|
|
|
|
|
|
def complete_dutch_translations():
|
|
"""Complete all missing Dutch translations."""
|
|
translations_dir = Path('translations')
|
|
nl_file = translations_dir / 'nl' / 'LC_MESSAGES' / 'messages.po'
|
|
en_file = translations_dir / 'en' / 'LC_MESSAGES' / 'messages.po'
|
|
|
|
if not nl_file.exists():
|
|
print(f"Error: Dutch translation file not found at {nl_file}")
|
|
return
|
|
|
|
if not en_file.exists():
|
|
print(f"Error: English translation file not found at {en_file}")
|
|
return
|
|
|
|
print("Reading translation files...")
|
|
nl_catalog = read_po(open(nl_file, 'r', encoding='utf-8'))
|
|
en_catalog = read_po(open(en_file, 'r', encoding='utf-8'))
|
|
|
|
print(f"Found {len(nl_catalog)} entries in Dutch file")
|
|
print(f"Found {len(en_catalog)} entries in English file")
|
|
|
|
# Find untranslated entries
|
|
untranslated = []
|
|
for message in nl_catalog:
|
|
if message.id:
|
|
# Check if translation is empty
|
|
is_empty = False
|
|
if isinstance(message.string, tuple):
|
|
# Plural form
|
|
is_empty = not message.string or all(not s for s in message.string)
|
|
else:
|
|
# Singular form
|
|
is_empty = not message.string or message.string == ""
|
|
|
|
if is_empty:
|
|
untranslated.append(message)
|
|
|
|
print(f"\nFound {len(untranslated)} untranslated entries")
|
|
|
|
if len(untranslated) == 0:
|
|
print("All translations are complete!")
|
|
return
|
|
|
|
# Show first 20 untranslated entries
|
|
print("\nFirst 20 untranslated entries:")
|
|
for i, msg in enumerate(untranslated[:20], 1):
|
|
msg_id = msg.id[:80] + "..." if len(msg.id) > 80 else msg.id
|
|
print(f"{i}. {msg_id}")
|
|
|
|
print(f"\n... and {len(untranslated) - 20} more entries")
|
|
|
|
# Ask for confirmation
|
|
response = input(f"\nDo you want to translate all {len(untranslated)} entries? (yes/no): ")
|
|
if response.lower() != 'yes':
|
|
print("Translation cancelled.")
|
|
return
|
|
|
|
# Translate entries using English as reference
|
|
translated_count = 0
|
|
for msg in untranslated:
|
|
# Try to find corresponding English message for context
|
|
en_msg = en_catalog.get(msg.id)
|
|
if en_msg and en_msg.string:
|
|
# For now, we'll use a simple approach: copy English as placeholder
|
|
# In production, you'd use a translation service or manual translation
|
|
# For this script, we'll mark them as needing translation
|
|
pass
|
|
|
|
print(f"\nTranslated {translated_count} entries")
|
|
print("Note: This script identifies untranslated entries.")
|
|
print("For actual translation, use a translation service or manual translation.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
complete_dutch_translations()
|
|
|