diff --git a/quickshell/scripts/i18nsync.py b/quickshell/scripts/i18nsync.py index 5afa14955..b82c65ff9 100755 --- a/quickshell/scripts/i18nsync.py +++ b/quickshell/scripts/i18nsync.py @@ -22,8 +22,15 @@ COMMON_POEXPORTS_DIR = COMMON_ROOT / "DankCommon" / "translations" / "poexports" # Plugin checkouts under quickshell/ are scanned by extraction (terms tagged # plugin-); their per-language exports are written back into each -# checkout's translations/ dir, which ships with the plugin repo. -PLUGIN_CHECKOUT_DIRS = [REPO_ROOT / "dms-plugins", REPO_ROOT / "dms-plugins-external"] +# checkout's translations/ dir, which ships with the plugin repo. sync +# clones and fast-forwards them: the official monorepo plus every registry +# plugin flagged "i18n": true, keyed by plugin id. +OFFICIAL_PLUGINS_REPO = "https://github.com/AvengeMedia/dms-plugins.git" +PLUGIN_REGISTRY_REPO = "https://github.com/AvengeMedia/dms-plugin-registry.git" +OFFICIAL_PLUGINS_DIR = REPO_ROOT / "dms-plugins" +EXTERNAL_PLUGINS_DIR = REPO_ROOT / "dms-plugins-external" +REGISTRY_DIR = EXTERNAL_PLUGINS_DIR / ".registry" +PLUGIN_CHECKOUT_DIRS = [OFFICIAL_PLUGINS_DIR, EXTERNAL_PLUGINS_DIR] # Flip once official plugins ship their own translations/ dirs: app poexports # then stop carrying terms owned exclusively by plugins. @@ -138,6 +145,45 @@ def load_greeter_entries(api_token, project_id): def entry_keys(entries): return {(e.get('context') or e['term'], e['term']) for e in entries} +def git(args, cwd): + result = subprocess.run(['git', *args], cwd=cwd, capture_output=True, text=True) + if result.returncode != 0: + error(f"git {' '.join(args)} in {cwd} failed:\n{result.stderr.strip()}") + +def clone_or_update(url, path): + rel = path.relative_to(REPO_ROOT) + if not (path / '.git').is_dir(): + info(f"Cloning {url} -> {rel}") + path.parent.mkdir(parents=True, exist_ok=True) + git(['clone', '--quiet', url, str(path)], REPO_ROOT) + return + info(f"Updating {rel}") + git(['pull', '--quiet', '--ff-only'], path) + +def i18n_registry_plugins(): + plugins = [] + for entry_file in sorted((REGISTRY_DIR / 'plugins').glob('*.json')): + with open(entry_file) as f: + entry = json.load(f) + if entry.get('i18n') is not True: + continue + if not entry.get('id') or not entry.get('repo'): + error(f"Registry entry {entry_file.name} has i18n set but no id or repo") + plugins.append((entry['id'], entry['repo'])) + return plugins + +def update_plugin_checkouts(): + clone_or_update(OFFICIAL_PLUGINS_REPO, OFFICIAL_PLUGINS_DIR) + clone_or_update(PLUGIN_REGISTRY_REPO, REGISTRY_DIR) + plugins = i18n_registry_plugins() + for plugin_id, repo in plugins: + clone_or_update(repo, EXTERNAL_PLUGINS_DIR / plugin_id) + approved = {plugin_id for plugin_id, _ in plugins} + for child in EXTERNAL_PLUGINS_DIR.iterdir(): + if child.is_dir() and not child.name.startswith('.') and child.name not in approved: + warn(f"dms-plugins-external/{child.name} is not flagged i18n in the registry but its terms still get uploaded; remove it if it was unapproved") + success(f"Plugin checkouts current: official + {len(plugins)} registry plugins") + def plugin_checkouts(): checkouts = {} for base in PLUGIN_CHECKOUT_DIRS: @@ -440,7 +486,7 @@ def main(): prune = "--prune" in sys.argv[2:] if prune: warn("--prune deletes every POEditor term missing from the local en.json, including its translations.") - warn("Terms from dms-plugins/ and dms-plugins-external/ are machine-dependent: make sure all official and approved external plugins are present before pruning.") + warn("Plugin checkouts are refreshed from the registry first, so pruning keeps terms of official and i18n-approved plugins; a plugin removed from the registry loses its terms.") warn("dank-qml-common terms are included from the submodule, so pruning keeps them as long as the submodule is current.") warn("dms-greeter terms are fetched from POEditor and re-included, so pruning keeps them.") @@ -449,6 +495,7 @@ def main(): greeter_entries = load_greeter_entries(api_token, project_id) greeter_keys = entry_keys(greeter_entries) + update_plugin_checkouts() extract_strings() current_en = normalize_json(EN_JSON)