@hoardodile/create-plugin 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,6 +43,9 @@ pnpm dev
43
43
  `src/hooks.ts`.
44
44
  - `src/shared.ts` — the plugin schema (`PluginSchema`) typed once and
45
45
  shared between the server and client sides.
46
+ - `src/i18n.ts` + `src/locales/` — plugin UI strings via
47
+ `createPluginTranslation`, one bundle per supported language (see
48
+ [Localization](#localization)).
46
49
  - `testdata/` — sample data for `pnpm dev`.
47
50
  - `__tests__/` — Vitest unit tests against `createResourceAPIFixture`.
48
51
 
@@ -112,6 +115,24 @@ git tag v<version> && git push origin v<version>
112
115
  Local installs (zip upload in **Settings → Plugins**) still work for
113
116
  private packages.
114
117
 
118
+ ## Localization
119
+
120
+ The app is localized in five languages (**en, zh, ja, de, es**). A plugin
121
+ can localize three surfaces:
122
+
123
+ 1. **Display name / description** — the `i18n` block in `manifest.json`
124
+ maps `name`/`description` (and any extra label keys) to a locale table;
125
+ the host picks the text for the user's UI language.
126
+ 2. **Plugin UI strings** — `src/i18n.ts` wraps `createPluginTranslation`
127
+ with one bundle per language in `src/locales/`; the hook starts in the
128
+ host's language and follows `languageChanged` pushes, falling back to
129
+ English for a language the plugin does not ship. `src/__tests__/locales.test.ts`
130
+ keeps the bundles in lockstep (key set, interpolation placeholders,
131
+ no untranslated English copy).
132
+ 3. **The marketplace readme** — see [Publishing a readme](#publishing-a-readme):
133
+ one `README.<locale>.md` per language, `README.md` as the English
134
+ fallback.
135
+
115
136
  ## Publishing a readme
116
137
 
117
138
  The marketplace detail view shows a per-release **Readme** tab. Ship the
@@ -13,11 +13,17 @@
13
13
  "i18n": {
14
14
  "name": {
15
15
  "en": "Template",
16
- "zh": "模板"
16
+ "zh": "模板",
17
+ "ja": "テンプレート",
18
+ "de": "Vorlage",
19
+ "es": "Plantilla"
17
20
  },
18
21
  "description": {
19
22
  "en": "Template plugin — copy this directory to start your own plugin",
20
- "zh": "模板插件——复制此目录作为你自己插件的起点"
23
+ "zh": "模板插件——复制此目录作为你自己插件的起点",
24
+ "ja": "テンプレートプラグイン — このディレクトリをコピーして独自プラグインを作成してください",
25
+ "de": "Vorlagen-Plugin — kopiere dieses Verzeichnis, um dein eigenes Plugin zu starten",
26
+ "es": "Plugin de plantilla — copia este directorio para empezar tu propio plugin"
21
27
  }
22
28
  }
23
29
  }
@@ -0,0 +1,19 @@
1
+ # Mein Plugin
2
+
3
+ _Ersetze diese Readme durch einen echten Überblick: welchen Inhalt das
4
+ Plugin erkennt, wie es ihn rendert und wo Nutzer ihn sehen._
5
+
6
+ Diese Readme wird mit **jedem GitHub-Release** veröffentlicht — bearbeite
7
+ sie pro Release, damit jede Version ihre eigenen Notizen im Marktplatz
8
+ präsentiert.
9
+
10
+ ## Funktionen
11
+
12
+ - Erkennt Ressourcen mit … und rendert sie inline.
13
+ - Deklariert folgende Berechtigungen: _siehe `manifest.json`_.
14
+
15
+ ## Voraussetzungen
16
+
17
+ - hoardodile ≥ 0.1.1 (Details im Repository-README).
18
+ - Vertraue dem Repository, bevor du installierst — Plugin-Code läuft
19
+ serverseitig in einer Sandbox.
@@ -0,0 +1,18 @@
1
+ # Mi plugin
2
+
3
+ _Sustituye esta readme por una descripción real: qué contenido detecta el
4
+ plugin, cómo lo renderiza y dónde lo verán las personas usuarias._
5
+
6
+ Esta readme se publica con **cada release de GitHub** — edítala en cada
7
+ versión para que cada release presente sus propias notas en el marketplace.
8
+
9
+ ## Funciones
10
+
11
+ - Detecta recursos que contienen … y los renderiza en línea.
12
+ - Declara los siguientes permisos: _consulta `manifest.json`_.
13
+
14
+ ## Requisitos
15
+
16
+ - hoardodile ≥ 0.1.1 (detalles en el README del repositorio).
17
+ - Confía en el repositorio antes de instalar: el código del plugin se
18
+ ejecuta en el servidor dentro de un sandbox restringido.
@@ -0,0 +1,18 @@
1
+ # 私のプラグイン
2
+
3
+ _この README を実際の概要に置き換えてください:プラグインが認識する内容、
4
+ そのレンダリング方法、ユーザーがどこで目にするかを説明します。_
5
+
6
+ この README は**すべての GitHub Release** と一緒に公開されます——リリースの
7
+ たびに編集し、各バージョンがマーケットプレイスでそれぞれの説明を提示できるようにしてください。
8
+
9
+ ## 機能
10
+
11
+ - ~を含むリソースを認識し、インラインでレンダリングします。
12
+ - 権限の宣言:_`manifest.json` を参照してください_。
13
+
14
+ ## 要件
15
+
16
+ - hoardodile ≥ 0.1.1(詳しくはリポジトリの README を参照)。
17
+ - インストール前にこのリポジトリを信頼してください——プラグインコードは
18
+ 制限付きサンドボックス内のサーバー側コードとして実行されます。
@@ -1,7 +1,9 @@
1
1
  import { usePluginAPI } from "./hooks"
2
+ import { useTranslation } from "./i18n"
2
3
 
3
4
  export function TemplateView() {
4
5
  const api = usePluginAPI()
6
+ const { t } = useTranslation()
5
7
  const files = api.resource.sourceMeta?.files ?? []
6
8
  const hdtplCount = api.resource.sourceMeta?.hdtplCount
7
9
 
@@ -10,14 +12,13 @@ export function TemplateView() {
10
12
  <header>
11
13
  <h1 className="text-lg font-semibold">{api.resource.name}</h1>
12
14
  <p className="text-gray-500 dark:text-gray-400">
13
- Rendered by the template plugin. Edit src/TemplateView.tsx to make it
14
- your own.
15
+ {t("renderedBy")} {t("editHint")}
15
16
  </p>
16
17
  </header>
17
18
  <p>
18
19
  {hdtplCount !== undefined
19
- ? `${hdtplCount} .hdtpl file(s) classified once by detect.`
20
- : "No sourceMeta yet."}
20
+ ? t("fileCount", { count: hdtplCount })
21
+ : t("noSourceMeta")}
21
22
  </p>
22
23
  <ul className="list-inside list-disc">
23
24
  {files.map((file) => (
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import de from "../locales/de"
3
+ import en from "../locales/en"
4
+ import es from "../locales/es"
5
+ import ja from "../locales/ja"
6
+ import zh from "../locales/zh"
7
+
8
+ const BUNDLES: Record<string, Record<string, unknown>> = {
9
+ en: en as unknown as Record<string, unknown>,
10
+ zh: zh as unknown as Record<string, unknown>,
11
+ ja: ja as unknown as Record<string, unknown>,
12
+ de: de as unknown as Record<string, unknown>,
13
+ es: es as unknown as Record<string, unknown>,
14
+ }
15
+
16
+ function flatten(
17
+ obj: Record<string, unknown>,
18
+ path: string[] = [],
19
+ out: { key: string; value: string }[] = [],
20
+ ): { key: string; value: string }[] {
21
+ for (const [k, v] of Object.entries(obj)) {
22
+ if (typeof v === "object" && v !== null) {
23
+ flatten(v as Record<string, unknown>, [...path, k], out)
24
+ } else {
25
+ out.push({ key: [...path, k].join("."), value: String(v) })
26
+ }
27
+ }
28
+ return out
29
+ }
30
+
31
+ function vars(value: string): string {
32
+ return (value.match(/\{\{[^}]+\}\}/g) ?? [])
33
+ .map((m) => m.slice(2, -2))
34
+ .sort()
35
+ .join(",")
36
+ }
37
+
38
+ describe("plugin locale parity", () => {
39
+ const enFlat = flatten(BUNDLES.en!)
40
+ const enKeys = new Set(enFlat.map((r) => r.key))
41
+
42
+ it("ships every hoardodile-supported language", () => {
43
+ expect(Object.keys(BUNDLES).sort()).toEqual(["de", "en", "es", "ja", "zh"])
44
+ })
45
+
46
+ it("has identical flat key sets across all bundles", () => {
47
+ for (const [lang, bundle] of Object.entries(BUNDLES)) {
48
+ const flat = flatten(bundle)
49
+ const keys = new Set(flat.map((r) => r.key))
50
+ expect(
51
+ [...enKeys].filter((k) => !keys.has(k)),
52
+ `keys only in en`,
53
+ ).toEqual([])
54
+ expect(
55
+ [...keys].filter((k) => !enKeys.has(k)),
56
+ `keys only in ${lang}`,
57
+ ).toEqual([])
58
+ }
59
+ })
60
+
61
+ it("uses the same interpolation placeholders per key", () => {
62
+ for (const [lang, bundle] of Object.entries(BUNDLES)) {
63
+ const byKey = new Map(flatten(bundle).map((r) => [r.key, r.value]))
64
+ const mismatched: string[] = []
65
+ for (const { key, value } of enFlat) {
66
+ const other = byKey.get(key)
67
+ if (other !== undefined && vars(value) !== vars(other)) {
68
+ mismatched.push(key)
69
+ }
70
+ }
71
+ expect(mismatched, `placeholder mismatch in ${lang}`).toEqual([])
72
+ }
73
+ })
74
+
75
+ it("never ships untranslated English source copy", () => {
76
+ const english = new Map(enFlat.map((r) => [r.key, r.value]))
77
+ for (const [lang, bundle] of Object.entries(BUNDLES)) {
78
+ if (lang === "en") continue
79
+ const untranslated: string[] = []
80
+ for (const { key, value } of flatten(bundle)) {
81
+ if (value === english.get(key)) untranslated.push(key)
82
+ }
83
+ expect(untranslated, `untranslated keys in ${lang}`).toEqual([])
84
+ }
85
+ })
86
+ })
@@ -0,0 +1,16 @@
1
+ import { createPluginTranslation } from "@hoardodile/sdk-react"
2
+ import de from "./locales/de"
3
+ import en from "./locales/en"
4
+ import es from "./locales/es"
5
+ import ja from "./locales/ja"
6
+ import zh from "./locales/zh"
7
+
8
+ /**
9
+ * Plugin UI strings, one bundle per hoardodile-supported language (see
10
+ * `@hoardodile/i18n` — en, zh, ja, de, es). The host's current language is
11
+ * picked automatically and follows `languageChanged` pushes; a language
12
+ * the plugin misses falls back to English.
13
+ */
14
+ const { useTranslation } = createPluginTranslation({ en, zh, ja, de, es })
15
+
16
+ export { useTranslation }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ renderedBy: "Gerendert vom Template-Plugin.",
3
+ editHint:
4
+ "Bearbeite src/TemplateView.tsx, um daraus dein eigenes Plugin zu machen.",
5
+ fileCount: "{{count}} .hdtpl-Datei(en) — einmal durch detect klassifiziert.",
6
+ noSourceMeta: "Noch keine sourceMeta.",
7
+ } as const
@@ -0,0 +1,6 @@
1
+ export default {
2
+ renderedBy: "Rendered by the template plugin.",
3
+ editHint: "Edit src/TemplateView.tsx to make it your own.",
4
+ fileCount: "{{count}} .hdtpl file(s) — classified once by detect.",
5
+ noSourceMeta: "No sourceMeta yet.",
6
+ } as const
@@ -0,0 +1,6 @@
1
+ export default {
2
+ renderedBy: "Renderizado por el plugin de plantilla.",
3
+ editHint: "Edita src/TemplateView.tsx para convertirlo en tu propio plugin.",
4
+ fileCount: "{{count}} archivo(s) .hdtpl — clasificados una vez por detect.",
5
+ noSourceMeta: "Aún no hay sourceMeta.",
6
+ } as const
@@ -0,0 +1,8 @@
1
+ export default {
2
+ renderedBy: "テンプレートプラグインで描画されています。",
3
+ editHint:
4
+ "src/TemplateView.tsx を編集して、自分用のプラグインに作り替えてください。",
5
+ fileCount:
6
+ "{{count}} 個の .hdtpl ファイル — detect により一度だけ分類されます。",
7
+ noSourceMeta: "sourceMeta はまだありません。",
8
+ } as const
@@ -0,0 +1,6 @@
1
+ export default {
2
+ renderedBy: "由模板插件渲染。",
3
+ editHint: "编辑 src/TemplateView.tsx,让它成为你自己的插件。",
4
+ fileCount: "{{count}} 个 .hdtpl 文件——由 detect 分类一次。",
5
+ noSourceMeta: "尚无 sourceMeta。",
6
+ } as const
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoardodile/create-plugin",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "license": "MIT",
5
5
  "description": "Scaffold a hoardodile content plugin.",
6
6
  "keywords": [
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@clack/prompts": "^1.7.0",
39
39
  "zod": "^4.4.3",
40
- "@hoardodile/sdk-types": "0.1.13"
40
+ "@hoardodile/sdk-types": "0.1.15"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^26.2.0",