General housekeeping in localisations

Signed-off-by: PanteraPolnocy <panterapolnocy@gmail.com>
This commit is contained in:
PanteraPolnocy
2026-06-19 19:21:51 +02:00
parent 42a73d455c
commit 5ca217c32b
10 changed files with 254 additions and 81 deletions
+251
View File
@@ -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), "&quot;")}" 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), "&quot;")}" 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), "&quot;")}" 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), "&quot;")}" 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), "&quot;")}" 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), "&quot;")}"',
line,
count=1,
)
else:
line = line.replace(
f'name="{name}"',
f'tool_tip="{value.replace(chr(34), "&quot;")}" 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())
@@ -2816,18 +2816,6 @@ Bu mesaj yenidən baş verərsə, kömək üçün http://support.secondlife.com
<string name="PicksClassifiedsLoadingText">
Yüklənir...
</string>
<string name="NoPicksText">
Seçilmiş yarartmamızınız.
</string>
<string name="NoAvatarPicksText">
İstifadəçinin seçilmişləri yoxdur
</string>
<string name="NoClassifiedsText">
Heç bir elan yaartmamızınız. Elan etmək üçün Yarat düyməsini basın.
</string>
<string name="NoAvatarClassifiedsText">
İstifadəçinin elanları yoxdur
</string>
<string name="MultiPreviewTitle">
Önizləmə
</string>
@@ -4915,12 +4915,6 @@ Erfahren Sie mehr unter https://second.life/scripted-agents.
<string name="share_alert">
Objekte aus dem Inventar hier her ziehen
</string>
<string name="facebook_post_success">
Sie haben auf Facebook gepostet.
</string>
<string name="facebook_post_success">
Sie haben auf Facebook gepostet.
</string>
<string name="flickr_post_success">
Sie haben auf Flickr gepostet.
</string>
@@ -5907,9 +5901,6 @@ Setzen Sie den Editorpfad in Anführungszeichen
<string name="Command_AboutLand_Label">
Landinformationen
</string>
<string name="Command_AboutLand_Label">
Landinformationen
</string>
<string name="Command_360_Capture_Label">
360° Foto
</string>
@@ -2615,10 +2615,6 @@ Si sigues recibiendo el mismo mensaje, solicita ayuda al personal de asistencia
<string name="PicksClassifiedsLoadingText">
Cargando...
</string>
<string name="NoPicksText">No has creado ningún destacado.</string>
<string name="NoAvatarPicksText">El usuario no tiene destacados</string>
<string name="NoClassifiedsText">No has creado ningún clasificado. Pulsa el botón '+' para crear uno.</string>
<string name="NoAvatarClassifiedsText">El usuario no tiene clasificados</string>
<string name="MultiPreviewTitle">
Vista previa
</string>
@@ -4598,9 +4594,6 @@ Más información en https://second.life/scripted-agents.
<string name="share_alert">
Arrastra aquí items del inventario
</string>
<string name="facebook_post_success">
Has publicado en Facebook.
</string>
<string name="flickr_post_success">
Has publicado en Flickr.
</string>
@@ -5391,7 +5391,7 @@ Vous ne pouvez pas annuler cette action.
</notification>
<notification name="RemoveContactsFromSet">
Êtes-vous sûr de vouloir supprimer ces avatars [TARGET] de [SET_NAME] ?
<usetemplate gnoretext="Confirmer avant de supprimer plusieurs avatars d'un groupe de contacts" name="okcancelignore" notext="Cancel" yestext="OK"/>
<usetemplate ignoretext="Confirmer avant de supprimer plusieurs avatars d'un groupe de contacts" name="okcancelignore" notext="Cancel" yestext="OK"/>
</notification>
<notification name="AddToContactSetSingleSuccess">
[NAME] a été ajouté à [SET].
@@ -2826,18 +2826,6 @@ Si vous continuez à recevoir ce message, veuillez contacter l'assistance de Sec
<string name="PicksClassifiedsLoadingText">
Chargement...
</string>
<string name="NoPicksText">
Vous n'avez pas créé de favoris.
</string>
<string name="NoAvatarPicksText">
L'utilisateur n'a pas de favoris.
</string>
<string name="NoClassifiedsText">
Vous n'avez pas créé d'annonces. Cliquez sur le bouton Plus ci-dessous pour créer une annonce.
</string>
<string name="NoAvatarClassifiedsText">
L'utilisateur n'a pas d'annonces
</string>
<string name="MultiPreviewTitle">
Prévisualiser
</string>
@@ -4863,9 +4851,6 @@ Pour en savoir plus, rendez-vous sur https://second.life/scripted-agents.
<string name="share_alert">
Faire glisser les objets de l&apos;inventaire ici
</string>
<string name="facebook_post_success">
Vous avez publié sur Facebook.
</string>
<string name="flickr_post_success">
Vous avez publié sur Flickr.
</string>
@@ -4774,9 +4774,6 @@ Scopri di più su https://second.life/scripted-agents.
<string name="share_alert">
Trascinare qui oggetti da inventario
</string>
<string name="facebook_post_success">
Hai pubblicato su Facebook.
</string>
<string name="flickr_post_success">
Hai pubblicato su Flickr.
</string>
@@ -465,9 +465,6 @@ https://secondlife.com/viewer-access-faq
<string name="TooltipFlagNoEdit">
編集禁止
</string>
<string name="TooltipFlagNoEdit">
グループ作成
</string>
<string name="TooltipFlagNotSafe">
危険
</string>
@@ -1320,9 +1317,6 @@ https://secondlife.com/viewer-access-faq
<string name="xml_file">
XMLファイル
</string>
<string name="csv_files">
CSVファイル
</string>
<string name="raw_file">
RAWファイル
</string>
@@ -2001,9 +1995,6 @@ https://support.secondlife.com よりSecond Lifeのサポートまでお問い
<string name="InvFolder Materials">
マテリアル
</string>
<string name="InvFolder Settings">
自然環境の設定
</string>
<!-- are used for Friends and Friends/All folders in Inventory "Calling cards" folder. See EXT-694-->
<string name="InvFolder Friends">
フレンド
@@ -2872,19 +2863,6 @@ https://support.secondlife.com よりSecond Lifeのサポートまでお問い
<string name="PicksClassifiedsLoadingText">
読み込んでいます…
</string>
<!-- FS:KC legacy profiles -->
<string name="NoPicksText">
ピックを作成していません。
</string>
<string name="NoAvatarPicksText">
このユーザーにはピックがありません。
</string>
<string name="NoClassifiedsText">
クラシファイド広告を作成していません。作成するには、下にある「+」ボタンをクリックします。
</string>
<string name="NoAvatarClassifiedsText">
このユーザーにはクラシファイド広告がありません。
</string>
<!-- Multi Preview Floater -->
<string name="MultiPreviewTitle">
プレビュー
@@ -5726,7 +5704,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ
<string name="Command_Move_Lock_Label">移動ロック</string>
<string name="Command_Blocklist_Label">ブロックリスト</string>
<string name="Command_ResyncAnimations_Label">アニメーション再同期</string>
<string name="Command_RegionTracker_Label">リージョントラッカー</string>
<string name="Command_Group_Titles_Label">グループのタイトル</string>
<string name="Command_Wearable_Favorites_Label">お気に入りの着用物やHUD</string>
<string name="Command_RFO_Label">フレンドのみ表示</string>
@@ -5815,9 +5792,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ
<string name="Command_Profile_Tooltip">
自分のプロフィールの表示や編集を行います。
</string>
<string name="Command_RegionTracker_Tooltip">
様々なリージョンの状況を追跡します。
</string>
<string name="Command_Report_Abuse_Tooltip">
嫌がらせを報告します。
</string>
@@ -6391,9 +6365,6 @@ Rez時間:[OBJECT_REZ_TIME]
<string name="preproc_toggle_warning">プリプロセッサを切り替えても、このエディターを閉じて再度開くまで完全には有効になりません。</string>
<!-- <FS:Cron> FIRE-9335 -->
<!-- <LSL Preprocessor -->
<string name="preproc_toggle_warning">
プリプロセッサの切り替えは、このエディタを閉じて再度開くまで完全には有効になりません。
</string>
<string name="fs_preprocessor_starting">
[APP_NAME]プリプロセッサを開始しています…
</string>
@@ -5364,9 +5364,6 @@ https://wiki.firestormviewer.org/fs_voice
<notification name="ExodusFlickrUploadComplete">
Ваш снимок теперь может быть просмотрен [https://www.flickr.com/photos/me/[ID] тут].
</notification>
<notification name="ExodusFlickrUploadComplete">
Ваш снимок теперь можно просмотреть [https://www.flickr.com/photos/me/[ID] здесь].
</notification>
<notification name="FSPrimfeedUploadComplete">
Ваш пост Primfeed теперь можно просмотреть [[PF_POSTURL] здесь].
</notification>
@@ -9,8 +9,8 @@
</fs_scroll_list>
<button name="add_needle" label="新增"/>
<button name="remove_needle" label="刪除"/>
<button name="up_needle" label="上移" tooltip="將規則上移 1 個位置。"/>
<button name="down_needle" label="下移" tooltip="將規則下移 1 個位置。"/>
<button name="up_needle" label="上移" tool_tip="將規則上移 1 個位置。"/>
<button name="down_needle" label="下移" tool_tip="將規則下移 1 個位置。"/>
</layout_panel>
<layout_panel name="filter_log_layout">
<fs_scroll_list name="filter_log">