From 5ca217c32bff33746d0e2fb2291213f992e48f34 Mon Sep 17 00:00:00 2001 From: PanteraPolnocy Date: Fri, 19 Jun 2026 19:21:51 +0200 Subject: [PATCH] General housekeeping in localisations Signed-off-by: PanteraPolnocy --- fsutils/translation_fix_bad_attributes.py | 251 ++++++++++++++++++ .../newview/skins/default/xui/az/strings.xml | 12 - .../newview/skins/default/xui/de/strings.xml | 9 - .../newview/skins/default/xui/es/strings.xml | 7 - .../skins/default/xui/fr/notifications.xml | 2 +- .../newview/skins/default/xui/fr/strings.xml | 15 -- .../newview/skins/default/xui/it/strings.xml | 3 - .../newview/skins/default/xui/ja/strings.xml | 29 -- .../skins/default/xui/ru/notifications.xml | 3 - .../default/xui/zh/floater_omnifilter.xml | 4 +- 10 files changed, 254 insertions(+), 81 deletions(-) create mode 100644 fsutils/translation_fix_bad_attributes.py diff --git a/fsutils/translation_fix_bad_attributes.py b/fsutils/translation_fix_bad_attributes.py new file mode 100644 index 0000000000..1f8c65bd1e --- /dev/null +++ b/fsutils/translation_fix_bad_attributes.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +"""Remove or relocate locale XUI attributes that do not exist on matching EN elements.""" + +from __future__ import annotations + +import re +import sys +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from translation_strings_auditing_tool import ( # noqa: E402 + SKINS_ROOT, + collect_files, + compare_extra_attributes, + extract_element_attrs, + parse_xml, +) + +STRUCTURAL_ATTRS = frozenset({ + "relwidth", "follows", "font", "initial_value", "label_width", "vlabel", + "unit_label", +}) + +ATTR_RE = r'(\s{attr}="[^"]*"|\s{attr}=\'[^\']*\')' +ATTR_RE_TMPL = r'(\s{attr}="[^"]*"|\s{attr}=\'[^\']*\')' + + +@dataclass +class FixAction: + element: str + attr: str + value: str + hint: str + en_attrs: dict[str, str] + + +def attr_pattern(attr: str) -> re.Pattern[str]: + return re.compile( + rf'\s+{re.escape(attr)}="[^"]*"|\s+{re.escape(attr)}=\'[^\']*\'', + ) + + +def line_has_name(line: str, name: str) -> bool: + return bool(re.search(rf'name\s*=\s*["\']{re.escape(name)}["\']', line)) + + +def remove_attr(line: str, attr: str) -> str: + return attr_pattern(attr).sub("", line, count=1) + + +def apply_line_fixes(line: str, actions: list[FixAction]) -> str: + if not any(line_has_name(line, a.element[1:]) for a in actions): + return line + + for action in actions: + name = action.element[1:] + if not line_has_name(line, name): + continue + + attr = action.attr + value = action.value + hint = action.hint + en_attrs = action.en_attrs + + if attr == "gnoretext": + line = remove_attr(line, "gnoretext") + if value and "ignoretext=" not in line: + line = line.replace( + f'name="{name}"', + f'ignoretext="{value.replace(chr(34), """)}" name="{name}"', + 1, + ) + continue + + if attr == "tooltip" and "tool_tip" in hint: + line = remove_attr(line, "tooltip") + if value and "tool_tip=" not in line: + line = line.replace( + f'name="{name}"', + f'tool_tip="{value.replace(chr(34), """)}" name="{name}"', + 1, + ) + continue + + if attr == "text" and "EN uses label" in hint: + line = remove_attr(line, "text") + continue + + if attr == "title" and "EN uses label" in hint: + line = remove_attr(line, "title") + if value and "label=" not in line: + line = line.replace( + f'name="{name}"', + f'label="{value.replace(chr(34), """)}" name="{name}"', + 1, + ) + continue + + if attr == "label" and "EN uses title" in hint: + line = remove_attr(line, "label") + if value and "title=" not in line: + line = line.replace( + f'name="{name}"', + f'title="{value.replace(chr(34), """)}" name="{name}"', + 1, + ) + continue + + if attr == "label" and "EN uses value" in hint: + line = remove_attr(line, "label") + if value and "value=" not in line: + line = line.replace( + f'name="{name}"', + f'value="{value.replace(chr(34), """)}" name="{name}"', + 1, + ) + continue + + if attr == "ignoretext" and f'name="{name}"' in line and 'name="okcancelbuttons"' in line: + line = line.replace('name="okcancelbuttons"', 'name="okcancelignore"', 1) + continue + + if attr == "label" and value and "tool_tip" in en_attrs and "label" not in en_attrs: + en_tip = en_attrs.get("tool_tip", "") + if "tool_tip=" in line: + tip_m = re.search(r'tool_tip="([^"]*)"', line) + loc_tip = tip_m.group(1) if tip_m else "" + if not loc_tip or loc_tip == en_tip: + line = re.sub( + r'tool_tip="[^"]*"', + f'tool_tip="{value.replace(chr(34), """)}"', + line, + count=1, + ) + else: + line = line.replace( + f'name="{name}"', + f'tool_tip="{value.replace(chr(34), """)}" name="{name}"', + 1, + ) + line = remove_attr(line, "label") + continue + + line = remove_attr(line, attr) + + return line + + +def fix_file_content(content: str, actions: list[FixAction]) -> tuple[str, int]: + by_name: dict[str, list[FixAction]] = defaultdict(list) + for action in actions: + by_name[action.element[1:]].append(action) + + lines = content.splitlines(keepends=True) + fixed = 0 + out: list[str] = [] + for line in lines: + new_line = line + for name, name_actions in by_name.items(): + if line_has_name(line, name): + before = new_line + new_line = apply_line_fixes(new_line, name_actions) + if new_line != before: + fixed += len(name_actions) + out.append(new_line) + return "".join(out), fixed + + +def fix_file(skin: str, locale: str, rel: str, issues) -> int: + locale_path = SKINS_ROOT / skin / "xui" / locale / rel + en_path = SKINS_ROOT / skin / "xui" / "en" / rel + en_root = parse_xml(en_path) + if en_root is None or not locale_path.exists(): + return 0 + + en_elems = extract_element_attrs(en_root, rel) + actions = [ + FixAction( + element=i.element, + attr=i.attr, + value=i.value, + hint=i.hint, + en_attrs=en_elems.get(i.element, {}), + ) + for i in issues + ] + + original = locale_path.read_text(encoding="utf-8") + updated, count = fix_file_content(original, actions) + if updated != original: + locale_path.write_text(updated, encoding="utf-8", newline="") + return count + + +def count_remaining() -> int: + return len(collect_all_issues()) + + +def collect_all_issues() -> list[tuple[str, str, str, object]]: + issues: list[tuple[str, str, str, object]] = [] + skins = sorted( + p.name for p in SKINS_ROOT.iterdir() + if p.is_dir() and (p / "xui" / "en").is_dir() + ) + for skin in skins: + en_files = collect_files(SKINS_ROOT / skin / "xui" / "en") + for loc_dir in sorted((SKINS_ROOT / skin / "xui").iterdir()): + if not loc_dir.is_dir() or loc_dir.name == "en": + continue + locale = loc_dir.name + loc_files = collect_files(loc_dir) + for rel in sorted(set(en_files) & set(loc_files)): + en_root = parse_xml(en_files[rel]) + loc_root = parse_xml(loc_files[rel]) + if en_root is None or loc_root is None: + continue + file_issues = compare_extra_attributes( + extract_element_attrs(en_root, rel), + extract_element_attrs(loc_root, rel), + ) + for issue in file_issues: + issues.append((skin, locale, rel, issue)) + return issues + + +def main() -> int: + total_fixed = 0 + files_touched = 0 + + grouped: dict[tuple[str, str, str], list] = defaultdict(list) + for skin, locale, rel, issue in collect_all_issues(): + grouped[(skin, locale, rel)].append(issue) + + for (skin, locale, rel), issues in sorted(grouped.items()): + count = fix_file(skin, locale, rel, issues) + if count: + files_touched += 1 + total_fixed += count + print(f"fixed {count:3d} {skin}/{locale}/{rel}") + + remaining = count_remaining() + print(f"\nTotal attributes fixed: {total_fixed} in {files_touched} files") + print(f"Remaining bad attributes: {remaining}") + return 0 if remaining == 0 else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/indra/newview/skins/default/xui/az/strings.xml b/indra/newview/skins/default/xui/az/strings.xml index bcf9a59688..87c90b558b 100644 --- a/indra/newview/skins/default/xui/az/strings.xml +++ b/indra/newview/skins/default/xui/az/strings.xml @@ -2816,18 +2816,6 @@ Bu mesaj yenidən baş verərsə, kömək üçün http://support.secondlife.com Yüklənir... - - Seçilmiş yarartmamızınız. - - - İstifadəçinin seçilmişləri yoxdur - - - Heç bir elan yaartmamızınız. Elan etmək üçün Yarat düyməsini basın. - - - İstifadəçinin elanları yoxdur - Önizləmə diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index 4c68c12789..19a647980a 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -4915,12 +4915,6 @@ Erfahren Sie mehr unter https://second.life/scripted-agents. Objekte aus dem Inventar hier her ziehen - - Sie haben auf Facebook gepostet. - - - Sie haben auf Facebook gepostet. - Sie haben auf Flickr gepostet. @@ -5907,9 +5901,6 @@ Setzen Sie den Editorpfad in Anführungszeichen Landinformationen - - Landinformationen - 360° Foto diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index 63944a7e92..9cb4ffaf00 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -2615,10 +2615,6 @@ Si sigues recibiendo el mismo mensaje, solicita ayuda al personal de asistencia Cargando... - No has creado ningún destacado. - El usuario no tiene destacados - No has creado ningún clasificado. Pulsa el botón '+' para crear uno. - El usuario no tiene clasificados Vista previa @@ -4598,9 +4594,6 @@ Más información en https://second.life/scripted-agents. Arrastra aquí items del inventario - - Has publicado en Facebook. - Has publicado en Flickr. diff --git a/indra/newview/skins/default/xui/fr/notifications.xml b/indra/newview/skins/default/xui/fr/notifications.xml index 4bf75a274d..260e2c5700 100644 --- a/indra/newview/skins/default/xui/fr/notifications.xml +++ b/indra/newview/skins/default/xui/fr/notifications.xml @@ -5391,7 +5391,7 @@ Vous ne pouvez pas annuler cette action. Êtes-vous sûr de vouloir supprimer ces avatars [TARGET] de [SET_NAME] ? - + [NAME] a été ajouté à [SET]. diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index e8a0b72a22..70a18d4bf8 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -2826,18 +2826,6 @@ Si vous continuez à recevoir ce message, veuillez contacter l'assistance de Sec Chargement... - - Vous n'avez pas créé de favoris. - - - L'utilisateur n'a pas de favoris. - - - Vous n'avez pas créé d'annonces. Cliquez sur le bouton Plus ci-dessous pour créer une annonce. - - - L'utilisateur n'a pas d'annonces - Prévisualiser @@ -4863,9 +4851,6 @@ Pour en savoir plus, rendez-vous sur https://second.life/scripted-agents. Faire glisser les objets de l'inventaire ici - - Vous avez publié sur Facebook. - Vous avez publié sur Flickr. diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index 66cb80816d..042ae95f10 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -4774,9 +4774,6 @@ Scopri di più su https://second.life/scripted-agents. Trascinare qui oggetti da inventario - - Hai pubblicato su Facebook. - Hai pubblicato su Flickr. diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index 1fb2f4132a..0ed2c97557 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -465,9 +465,6 @@ https://secondlife.com/viewer-access-faq 編集禁止 - - グループ作成 - 危険 @@ -1320,9 +1317,6 @@ https://secondlife.com/viewer-access-faq XMLファイル - - CSVファイル - RAWファイル @@ -2001,9 +1995,6 @@ https://support.secondlife.com よりSecond Lifeのサポートまでお問い マテリアル - - 自然環境の設定 - フレンド @@ -2872,19 +2863,6 @@ https://support.secondlife.com よりSecond Lifeのサポートまでお問い 読み込んでいます… - - - ピックを作成していません。 - - - このユーザーにはピックがありません。 - - - クラシファイド広告を作成していません。作成するには、下にある「+」ボタンをクリックします。 - - - このユーザーにはクラシファイド広告がありません。 - プレビュー @@ -5726,7 +5704,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ 移動ロック ブロックリスト アニメーション再同期 - リージョントラッカー グループのタイトル お気に入りの着用物やHUD フレンドのみ表示 @@ -5815,9 +5792,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ 自分のプロフィールの表示や編集を行います。 - - 様々なリージョンの状況を追跡します。 - 嫌がらせを報告します。 @@ -6391,9 +6365,6 @@ Rez時間:[OBJECT_REZ_TIME] プリプロセッサを切り替えても、このエディターを閉じて再度開くまで完全には有効になりません。 - - プリプロセッサの切り替えは、このエディタを閉じて再度開くまで完全には有効になりません。 - [APP_NAME]プリプロセッサを開始しています… diff --git a/indra/newview/skins/default/xui/ru/notifications.xml b/indra/newview/skins/default/xui/ru/notifications.xml index fc03386c40..cf36c61786 100644 --- a/indra/newview/skins/default/xui/ru/notifications.xml +++ b/indra/newview/skins/default/xui/ru/notifications.xml @@ -5364,9 +5364,6 @@ https://wiki.firestormviewer.org/fs_voice Ваш снимок теперь может быть просмотрен [https://www.flickr.com/photos/me/[ID] тут]. - - Ваш снимок теперь можно просмотреть [https://www.flickr.com/photos/me/[ID] здесь]. - Ваш пост Primfeed теперь можно просмотреть [[PF_POSTURL] здесь]. diff --git a/indra/newview/skins/default/xui/zh/floater_omnifilter.xml b/indra/newview/skins/default/xui/zh/floater_omnifilter.xml index 85c82c6ae1..5ddf039aaf 100644 --- a/indra/newview/skins/default/xui/zh/floater_omnifilter.xml +++ b/indra/newview/skins/default/xui/zh/floater_omnifilter.xml @@ -9,8 +9,8 @@