mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-09-14 21:11:33 +00:00
Implement improve mode
In improve mode, the AI is allowed to change any translation to correct mistakes of the old Google Translate code.
This commit is contained in:
76
translate.py
76
translate.py
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import argparse
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
from google import genai
|
from google import genai
|
||||||
@@ -135,7 +136,24 @@ def build_translation_prompt(
|
|||||||
english_comments: List[str],
|
english_comments: List[str],
|
||||||
existing_translated_comments: List[str],
|
existing_translated_comments: List[str],
|
||||||
generator_comment_en: str,
|
generator_comment_en: str,
|
||||||
|
improve_mode: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
if improve_mode:
|
||||||
|
existing_header = "Here are previous translations for this language (you may reuse them or improve them; keep unchanged if already correct):"
|
||||||
|
items_header = "Here are the strings to review and output FINAL translations for (provide a value for every key; if keeping the existing translation, repeat it verbatim):"
|
||||||
|
mode_rules = """
|
||||||
|
Improve mode rules:
|
||||||
|
- You are revising existing translations for a smartwatch UI.
|
||||||
|
- For each string:
|
||||||
|
- If the English source text changed in meaning, update the translation accordingly.
|
||||||
|
- If the existing translation has grammar or style issues, or you are certain a different translation is a better fit (more natural, concise, and consistent with UI), provide an improved translation.
|
||||||
|
- If the existing translation is already accurate, natural, and consistent, you may keep it unchanged by returning the same text.
|
||||||
|
""".strip()
|
||||||
|
else:
|
||||||
|
existing_header = "Here are existing translations for this language (do not modify these; use for terminology/style consistency):"
|
||||||
|
items_header = "Here are the ONLY strings that need new translations (translate the values):"
|
||||||
|
mode_rules = ""
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
You are a professional localizer for a smartwatch UI. Translate UI strings into {language_name}.
|
You are a professional localizer for a smartwatch UI. Translate UI strings into {language_name}.
|
||||||
|
|
||||||
@@ -150,6 +168,7 @@ Rules:
|
|||||||
- Keep meaning accurate and UI-appropriate (short, natural, consistent).
|
- Keep meaning accurate and UI-appropriate (short, natural, consistent).
|
||||||
- Use consistent terminology aligned with existing translations for this language.
|
- Use consistent terminology aligned with existing translations for this language.
|
||||||
- Do NOT translate the string IDs themselves.
|
- Do NOT translate the string IDs themselves.
|
||||||
|
{("\n" + mode_rules) if mode_rules else ""}
|
||||||
|
|
||||||
Comments handling:
|
Comments handling:
|
||||||
- You are given comments from the English XML (in order) and the current translations (same order where available).
|
- You are given comments from the English XML (in order) and the current translations (same order where available).
|
||||||
@@ -160,10 +179,10 @@ Comments handling:
|
|||||||
Here are the complete English strings for context:
|
Here are the complete English strings for context:
|
||||||
{json.dumps(english_full, ensure_ascii=False, indent=2)}
|
{json.dumps(english_full, ensure_ascii=False, indent=2)}
|
||||||
|
|
||||||
Here are existing translations for this language (do not modify these; use for terminology/style consistency):
|
{existing_header}
|
||||||
{json.dumps(existing_translations, ensure_ascii=False, indent=2)}
|
{json.dumps(existing_translations, ensure_ascii=False, indent=2)}
|
||||||
|
|
||||||
Here are the ONLY strings that need new translations (translate the values):
|
{items_header}
|
||||||
{json.dumps(to_translate, ensure_ascii=False, indent=2)}
|
{json.dumps(to_translate, ensure_ascii=False, indent=2)}
|
||||||
|
|
||||||
Comments to translate (same order as in the XML):
|
Comments to translate (same order as in the XML):
|
||||||
@@ -193,6 +212,8 @@ def translate_language(
|
|||||||
lang_tuple: Tuple[str, str, str],
|
lang_tuple: Tuple[str, str, str],
|
||||||
english_soup: BeautifulSoup,
|
english_soup: BeautifulSoup,
|
||||||
english_strings: Dict[str, str],
|
english_strings: Dict[str, str],
|
||||||
|
verbose: bool = False,
|
||||||
|
improve: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
garmin_code, _unused, language_name = lang_tuple
|
garmin_code, _unused, language_name = lang_tuple
|
||||||
|
|
||||||
@@ -226,7 +247,7 @@ def translate_language(
|
|||||||
else:
|
else:
|
||||||
generator_comment_en = f"Generated by {MODEL_NAME} from English to {language_name}"
|
generator_comment_en = f"Generated by {MODEL_NAME} from English to {language_name}"
|
||||||
|
|
||||||
# Decide which strings need translation (not in corrections, not in previous)
|
# Decide which strings need translation
|
||||||
to_translate_map: Dict[str, str] = {}
|
to_translate_map: Dict[str, str] = {}
|
||||||
final_values: Dict[str, str] = {}
|
final_values: Dict[str, str] = {}
|
||||||
|
|
||||||
@@ -234,21 +255,33 @@ def translate_language(
|
|||||||
sid = s.get("id")
|
sid = s.get("id")
|
||||||
if not sid:
|
if not sid:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Always keep English as-is for exception IDs
|
||||||
if sid in exceptionIds:
|
if sid in exceptionIds:
|
||||||
# Keep English as-is for exception IDs
|
|
||||||
final_values[sid] = s.get_text()
|
final_values[sid] = s.get_text()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Respect corrections.xml as authoritative
|
||||||
if sid in corrections_map and corrections_map[sid] is not None:
|
if sid in corrections_map and corrections_map[sid] is not None:
|
||||||
final_values[sid] = corrections_map[sid]
|
final_values[sid] = corrections_map[sid]
|
||||||
elif sid in prev_map and prev_map[sid] is not None:
|
continue
|
||||||
final_values[sid] = prev_map[sid]
|
|
||||||
else:
|
|
||||||
to_translate_map[sid] = s.get_text()
|
|
||||||
|
|
||||||
# If there are no new strings to translate, skip this language entirely
|
if improve:
|
||||||
|
# Improve mode: reprocess all remaining strings
|
||||||
|
to_translate_map[sid] = s.get_text()
|
||||||
|
else:
|
||||||
|
# Normal mode: translate only new strings
|
||||||
|
if sid in prev_map and prev_map[sid] is not None:
|
||||||
|
final_values[sid] = prev_map[sid]
|
||||||
|
else:
|
||||||
|
to_translate_map[sid] = s.get_text()
|
||||||
|
|
||||||
|
# If there are no strings to translate (e.g., all covered by corrections), skip
|
||||||
if not to_translate_map:
|
if not to_translate_map:
|
||||||
print(f" Skipping {language_name}: no new strings to translate.")
|
reason = "no strings to translate (all covered by corrections or exceptions)"
|
||||||
|
if not improve:
|
||||||
|
reason = "no new strings to translate."
|
||||||
|
print(f" Skipping {language_name}: {reason}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Prepare context (always include full English strings)
|
# Prepare context (always include full English strings)
|
||||||
@@ -266,8 +299,12 @@ def translate_language(
|
|||||||
english_comments=english_comments,
|
english_comments=english_comments,
|
||||||
existing_translated_comments=existing_translated_comments,
|
existing_translated_comments=existing_translated_comments,
|
||||||
generator_comment_en=generator_comment_en,
|
generator_comment_en=generator_comment_en,
|
||||||
|
improve_mode=improve,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(prompt)
|
||||||
|
|
||||||
config = genai.types.GenerateContentConfig(
|
config = genai.types.GenerateContentConfig(
|
||||||
temperature=0,
|
temperature=0,
|
||||||
response_mime_type="application/json",
|
response_mime_type="application/json",
|
||||||
@@ -291,6 +328,9 @@ def translate_language(
|
|||||||
raise RuntimeError("Empty response from model; cannot parse translations.")
|
raise RuntimeError("Empty response from model; cannot parse translations.")
|
||||||
data = json.loads(txt)
|
data = json.loads(txt)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(data)
|
||||||
|
|
||||||
translations = data.get("translations", {}) or {}
|
translations = data.get("translations", {}) or {}
|
||||||
for sid, translated in translations.items():
|
for sid, translated in translations.items():
|
||||||
if sid in to_translate_map:
|
if sid in to_translate_map:
|
||||||
@@ -327,6 +367,11 @@ def translate_language(
|
|||||||
w.write(soup.encode("utf-8") + b"\n")
|
w.write(soup.encode("utf-8") + b"\n")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Translate Garmin IQ strings.xml using Gemini.")
|
||||||
|
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose debug output (prints prompts and responses)")
|
||||||
|
parser.add_argument("-i", "--improve", action="store_true", help="Improve mode: re-run all strings through translation for potential improvements")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Init client
|
# Init client
|
||||||
client = genai.Client()
|
client = genai.Client()
|
||||||
|
|
||||||
@@ -342,9 +387,16 @@ def main():
|
|||||||
|
|
||||||
total_langs = len(languages)
|
total_langs = len(languages)
|
||||||
for i, lang in enumerate(languages, start=1):
|
for i, lang in enumerate(languages, start=1):
|
||||||
print(f"{i} of {total_langs}: Translating English to {lang[2]}")
|
print(f"{i} of {total_langs}: Translating English to {lang[2]}" + (" [improve]" if args.improve else ""))
|
||||||
try:
|
try:
|
||||||
translate_language(client, lang, english_soup, english_strings)
|
translate_language(
|
||||||
|
client=client,
|
||||||
|
lang_tuple=lang,
|
||||||
|
english_soup=english_soup,
|
||||||
|
english_strings=english_strings,
|
||||||
|
verbose=args.verbose,
|
||||||
|
improve=args.improve,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Error translating {lang[2]}: {e}")
|
print(f" Error translating {lang[2]}: {e}")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user