From 6eb55131d995bd95c5860f64475a0fa744294a54 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Sun, 26 Jul 2026 22:05:38 +0200 Subject: [PATCH 1/2] feat(registry): localized store descriptions via description_i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store cards render registry.json verbatim, so the Apple Motion Artwork description stayed English in all 17 app languages while the chrome around it was translated. Add an optional `description_i18n` map next to `description` and fill it for the 16 non-English locales. WaveFlow 1.8.0+ picks the user's language (exact code -> base language -> en -> any entry); older builds ignore the unknown field and keep rendering the plain English string. `description` deliberately stays a plain string. This file is fetched by every WaveFlow ever installed, and a build predating localized descriptions fails to decode the whole catalogue if it finds an object there — its store would go dark for every plugin, and min_app_version can't rescue it since it is read after that decode. The schema now says so, CONTRIBUTING documents the sibling-table rule for both registry entries and plugin manifests, and additionalProperties:false meant the schema had to learn the new field anyway. Verified: ajv validation passes as in CI, and the v1.7.0 wire structs still decode this file. Refs InstaZDLL/WaveFlow#440 --- CONTRIBUTING.md | 26 ++++++++++++++++++++++++++ README.md | 3 +++ registry.json | 18 ++++++++++++++++++ schema/registry.schema.json | 9 ++++++++- 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67fe52e..7bc2af8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,6 +52,31 @@ storage_state = true Declare the **narrowest** HTTP allowlist that works — it's shown to the user before install and enforced at runtime. +### Translating what the user reads + +WaveFlow ships in 17 languages, but your manifest lives outside its translation files — so the app can only show what you write here. Add the translations in a **sibling `*_i18n` table**, leaving the English string exactly where it is: + +```toml +[plugin] +description = "Fetches Apple Music's animated album covers." + +[plugin.description_i18n] +fr = "Récupère les pochettes animées d'Apple Music." +de = "Holt animierte Albumcover von Apple Music." + +[[options]] +key = "prefer_hevc" +type = "bool" +label = "Prefer 4K HEVC covers" + +[options.label_i18n] +fr = "Préférer les pochettes 4K HEVC" +``` + +Locale codes: `en` `fr` `de` `es` `it` `nl` `pt` `pt-BR` `ru` `tr` `id` `ja` `ko` `zh-CN` `zh-TW` `ar` `hi`. Any subset is fine — WaveFlow falls back exact code → base language (`pt-BR` → `pt`) → `en` → any entry. Keep brand and codec tokens (`WaveFlow`, `Apple Music`, `Last.fm`, `HEVC`…) verbatim. + +> **Do not replace the string with a language map.** WaveFlow ≥ 1.8.0 also accepts `[plugin.description]` as an inline `en = … / fr = …` table, but a host predating that expects a string, errors on the table, and **drops your plugin entirely**. The sibling form is ignored by old hosts and understood by new ones, so it costs you nothing and needs no `min_app_version` bump. + ## 4. Release it Tag a semver release in your plugin repo. Copy [`templates/plugin-release.yml`](templates/plugin-release.yml) into `.github/workflows/` — it is a **single job** (no OS matrix) that: @@ -68,6 +93,7 @@ Open a PR against this repo adding your entry to [`registry.json`](registry.json - `version` = the release tag (without `v`). - `blake3` = the hash the release workflow printed. **This is what the app verifies.** Get it wrong and installs fail closed. - `permissions` = a byte-for-byte mirror of your `manifest.toml` `[permissions]`. +- `description` = a plain string, **always**. `registry.json` is one document fetched by every WaveFlow ever installed; an older build that finds an object here fails to decode the entire catalogue from all three delivery paths and its store goes dark — for every plugin, not just yours. Translations go in the optional `description_i18n` map next to it, which old builds ignore. CI validates your entry against [the schema](schema/registry.schema.json). Once merged, your plugin appears in every user's in-app store within minutes (raw.githubusercontent + jsDelivr are the delivery paths). diff --git a/README.md b/README.md index 4ec33f0..8bbedc9 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ Every entry is validated against [`schema/registry.schema.json`](schema/registry "id": "apple-artwork", "name": "Apple Motion Artwork", "description": "Fetches Apple Music's animated album covers and plays them behind the now-playing view.", + "description_i18n": { + "fr": "Récupère les pochettes animées d'Apple Music et les affiche derrière la vue En cours de lecture." + }, "author": "InstaZDLL", "repo": "InstaZDLL/waveflow-plugin-apple-artwork", "homepage": "https://github.com/InstaZDLL/waveflow-plugin-apple-artwork", diff --git a/registry.json b/registry.json index 485c99c..213317d 100644 --- a/registry.json +++ b/registry.json @@ -6,6 +6,24 @@ "id": "apple-artwork", "name": "Apple Motion Artwork", "description": "Animated album covers from Apple Music, rendered behind the now-playing view.", + "description_i18n": { + "fr": "Pochettes d'album animées depuis Apple Music, affichées derrière la vue En cours de lecture.", + "de": "Animierte Albumcover aus Apple Music, hinter der Ansicht „Wird gespielt“ dargestellt.", + "es": "Portadas de álbum animadas de Apple Music, mostradas detrás de la vista Reproduciendo ahora.", + "it": "Copertine animate degli album da Apple Music, mostrate dietro la vista In riproduzione.", + "nl": "Geanimeerde albumhoezen van Apple Music, weergegeven achter de weergave Speelt nu.", + "pt": "Capas de álbum animadas do Apple Music, apresentadas por trás da vista Agora a reproduzir.", + "pt-BR": "Capas de álbum animadas do Apple Music, exibidas atrás da tela Tocando agora.", + "ru": "Анимированные обложки альбомов из Apple Music, отображаются позади экрана «Сейчас играет».", + "tr": "Apple Music'ten animasyonlu albüm kapakları, Şimdi çalıyor görünümünün arkasında gösterilir.", + "id": "Sampul album animasi dari Apple Music, ditampilkan di belakang tampilan Sedang diputar.", + "ja": "Apple Music のアニメーションアルバムカバーを「再生中」画面の背景に表示します。", + "ko": "Apple Music의 애니메이션 앨범 커버를 '지금 재생 중' 화면 뒤에 표시합니다.", + "zh-CN": "来自 Apple Music 的动态专辑封面,显示在“正在播放”视图后方。", + "zh-TW": "來自 Apple Music 的動態專輯封面,顯示於「現在播放」畫面後方。", + "ar": "أغلفة ألبومات متحركة من Apple Music، تُعرض خلف واجهة «قيد التشغيل».", + "hi": "Apple Music से एनिमेटेड एल्बम कवर, \"अब चल रहा है\" व्यू के पीछे दिखाए जाते हैं।" + }, "author": "InstaZDLL", "repo": "InstaZDLL/waveflow-plugin-apple-artwork", "homepage": "https://github.com/InstaZDLL/waveflow-plugin-apple-artwork", diff --git a/schema/registry.schema.json b/schema/registry.schema.json index 6f14c0a..294698d 100644 --- a/schema/registry.schema.json +++ b/schema/registry.schema.json @@ -30,7 +30,14 @@ "description": "Stable plugin id. MUST equal the install-dir name and the manifest.toml [plugin] id — the app refuses a mismatch." }, "name": { "type": "string", "minLength": 1, "description": "Human-readable display name." }, - "description": { "type": "string", "minLength": 1, "description": "One or two sentences shown on the store card." }, + "description": { "type": "string", "minLength": 1, "description": "One or two sentences shown on the store card. MUST stay a plain string: this file is fetched by every WaveFlow ever installed, and a build predating localized descriptions fails to decode the whole catalogue if it finds an object here — its store goes dark. Translations belong in description_i18n." }, + "description_i18n": { + "type": "object", + "minProperties": 1, + "additionalProperties": { "type": "string", "minLength": 1 }, + "propertyNames": { "pattern": "^[a-z]{2}(?:-[A-Za-z]{2,4})?$" }, + "description": "Translations of `description`, keyed by the app's locale codes (en, fr, de, es, it, nl, pt, pt-BR, ru, tr, id, ja, ko, zh-CN, zh-TW, ar, hi). WaveFlow 1.8.0+ shows the entry in the user's language and falls back exact code → base language → en → any entry; older builds ignore this field and render `description` as before. Keep brand tokens (WaveFlow, Apple Music, Last.fm, HEVC…) verbatim." + }, "author": { "type": "string", "minLength": 1 }, "repo": { "type": "string", From 5a4f15eed4a33f7026f4d4264afcdc19db08164b Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Sun, 26 Jul 2026 22:29:04 +0200 Subject: [PATCH 2/2] fix(registry): russian wording + pin locale keys to the shipped codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two review findings: - the Russian description put a comma between subject and predicate. Switched to the participle form (отображаемые), which also mirrors the English noun phrase more closely than a finite verb did. - the locale pattern accepted any two-letter code and any suffix, while the app resolves keys verbatim against its 17 codes. A typo like "pt-br" or "jp" would have validated and then silently never rendered. An explicit enum fails it closed at review time instead. Verified with the CI validator: registry.json still passes and a "pt-br" key is now rejected. --- registry.json | 2 +- schema/registry.schema.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/registry.json b/registry.json index 213317d..f828ffb 100644 --- a/registry.json +++ b/registry.json @@ -14,7 +14,7 @@ "nl": "Geanimeerde albumhoezen van Apple Music, weergegeven achter de weergave Speelt nu.", "pt": "Capas de álbum animadas do Apple Music, apresentadas por trás da vista Agora a reproduzir.", "pt-BR": "Capas de álbum animadas do Apple Music, exibidas atrás da tela Tocando agora.", - "ru": "Анимированные обложки альбомов из Apple Music, отображаются позади экрана «Сейчас играет».", + "ru": "Анимированные обложки альбомов из Apple Music, отображаемые позади экрана «Сейчас играет».", "tr": "Apple Music'ten animasyonlu albüm kapakları, Şimdi çalıyor görünümünün arkasında gösterilir.", "id": "Sampul album animasi dari Apple Music, ditampilkan di belakang tampilan Sedang diputar.", "ja": "Apple Music のアニメーションアルバムカバーを「再生中」画面の背景に表示します。", diff --git a/schema/registry.schema.json b/schema/registry.schema.json index 294698d..63dae83 100644 --- a/schema/registry.schema.json +++ b/schema/registry.schema.json @@ -35,7 +35,10 @@ "type": "object", "minProperties": 1, "additionalProperties": { "type": "string", "minLength": 1 }, - "propertyNames": { "pattern": "^[a-z]{2}(?:-[A-Za-z]{2,4})?$" }, + "propertyNames": { + "enum": ["en", "fr", "de", "es", "it", "nl", "pt", "pt-BR", "ru", "tr", "id", "ja", "ko", "zh-CN", "zh-TW", "ar", "hi"], + "description": "Exactly the codes WaveFlow ships, case included. A permissive pattern would wave through a typo like \"pt-br\" or \"jp\": the app looks the key up verbatim, so the entry would simply never be shown, silently. Add a code here when WaveFlow adds a locale." + }, "description": "Translations of `description`, keyed by the app's locale codes (en, fr, de, es, it, nl, pt, pt-BR, ru, tr, id, ja, ko, zh-CN, zh-TW, ar, hi). WaveFlow 1.8.0+ shows the entry in the user's language and falls back exact code → base language → en → any entry; older builds ignore this field and render `description` as before. Keep brand tokens (WaveFlow, Apple Music, Last.fm, HEVC…) verbatim." }, "author": { "type": "string", "minLength": 1 },