@vestra-player/sdk 1.5.8 → 1.5.9

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.
package/llms-full.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @vestra-player/sdk 1.5.8: full reference
1
+ # @vestra-player/sdk 1.5.9: full reference
2
2
 
3
3
  > Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
4
4
 
@@ -1109,8 +1109,10 @@ interface Scope extends Disposable {
1109
1109
  readonly timeline: Timeline;
1110
1110
  /** Files dropped on the video area, by extension (`ui.drop`). */
1111
1111
  readonly drops: Drops;
1112
- /** The player's palette. */
1112
+ /** The player's palette and the saved interface language. */
1113
1113
  readonly appearance: Appearance;
1114
+ /** The interface language of this run (read-only). */
1115
+ readonly i18n: I18n;
1114
1116
  /** Pages in the player's options window (`ui.settings`). */
1115
1117
  readonly settings: Settings;
1116
1118
  /** A child scope; disposing it releases only what was created through it. The child is itself owned by this scope. */
@@ -1216,9 +1218,9 @@ type Bindable<T> = T | (() => T);
1216
1218
  ### MenuId
1217
1219
 
1218
1220
  A built-in top-level menu of the player, named in a command path as
1219
- `@<id>` (see CommandOptions.path): Open (打开), Playback (播放),
1220
- Subtitles (字幕), Video (视频), Audio (声音), Screen (屏幕), Settings (设置).
1221
- The list is `MENU_IDS` in `@vestra-player/sdk/testing`.
1221
+ `@<id>` or by its English key (see CommandOptions.path): Open,
1222
+ Playback, Subtitles, Video, Audio, Screen, Settings. The list is
1223
+ `MENU_IDS` in `@vestra-player/sdk/testing`.
1222
1224
 
1223
1225
  ```ts
1224
1226
  type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "screen" | "settings";
@@ -1239,7 +1241,7 @@ Built-in menus keep their place. The seven built-in top-level menus
1239
1241
  (MenuId) sit at fixed orders, whatever is inside them: Open 110,
1240
1242
  Playback 200, Subtitles 300, Video 400, Audio 500, Screen 710, Settings
1241
1243
  830 (player 0.3.0). This holds whether the path names the menu by id
1242
- (`["@video", "Blur"]`) or by its title (`["视频", "Blur"]`), so a plugin's
1244
+ (`["@video", "Blur"]`) or by its key (`["Video", "Blur"]`), so a plugin's
1243
1245
  item never moves the player's menu. Directly inside a built-in menu,
1244
1246
  `order` 0 (the default) sorts after every other order: an item without
1245
1247
  `order` goes at the end, after the player's own items (which all use 100
@@ -1263,16 +1265,23 @@ interface CommandOptions {
1263
1265
  * one segment is a top-level item. Omit it for a command that is not in the
1264
1266
  * main menu (bindings and panel menus only); `title` is then required.
1265
1267
  *
1266
- * Segments are menu titles, matched exactly. The player's own menus have
1267
- * Chinese titles, so `["Video", "Blur"]` would add a second top-level menu
1268
- * "Video". To join a built-in menu, start the path with its id, `@` plus a
1269
- * MenuId: `@open`, `@playback`, `@subtitles`, `@video`, `@audio`,
1270
- * `@screen` or `@settings`. Only the first segment is read as an id; an
1271
- * unknown id, or an id with nothing after it, fails with `invalid`. Needs
1272
- * player 0.3.0 (set `"minPlayerVersion": "0.3.0"`).
1268
+ * Segments are English source text, and menus merge by them: commands
1269
+ * of different plugins with the same leading segments share a submenu.
1270
+ * The player shows each segment and the title in the interface language
1271
+ * from the declaring plugin's catalog (`locales/<lang>.po`, see the
1272
+ * i18n guide); the player's own menus
1273
+ * (Open, Playback, Subtitles, Video, Audio, Screen, Settings, and
1274
+ * Playback ▸ Speed, Playback ▸ Seek) always show the player's words.
1275
+ * A built-in menu can also be named by its id, `@` plus a MenuId:
1276
+ * `@open`, `@playback`, `@subtitles`, `@video`, `@audio`, `@screen` or
1277
+ * `@settings`. Only the first segment is read as an id; an unknown id, or
1278
+ * an id with nothing after it, fails with `invalid`. Players up to 0.4.2
1279
+ * titled their menus in Chinese (视频…): there `["Video", "Blur"]` is a
1280
+ * menu of its own, so plugins for those players write `@video`; player
1281
+ * 0.4.3 still accepts the old Chinese titles of its own menus.
1273
1282
  */
1274
1283
  path?: string[];
1275
- /** Menu label; defaults to the last path segment. */
1284
+ /** Menu label (English source text, shown translated); defaults to the last path segment. */
1276
1285
  title?: string;
1277
1286
  /**
1278
1287
  * Sibling order in the menu, ascending (integer, default 0). A submenu sits where its first command sits, except a built-in menu, which keeps its fixed place. Directly inside a built-in menu, 0 (the default) sorts last. Equal orders: plugin id, then declaration order. Range ±1,000,000. See CommandOptions for the menu order rules.
@@ -1901,6 +1910,64 @@ interface Appearance {
1901
1910
  * @throws capability (rejects) The manifest does not declare `ui.theme`.
1902
1911
  */
1903
1912
  set(theme: ThemeId): Promise<void>;
1913
+ /** The saved interface language: `"system"` (follow the system's languages, the default) or a Locale. */
1914
+ readonly language: LanguagePreference;
1915
+ /**
1916
+ * Save the interface language for the next start.
1917
+ * @capability ui.theme
1918
+ * @player 0.4.3
1919
+ * @param language - `"system"` or a Locale.
1920
+ * @returns Resolves once the player saved it; Appearance.language has the new value.
1921
+ * @throws invalid (rejects) `language` is not `"system"` or a Locale.
1922
+ * @throws capability (rejects) The manifest does not declare `ui.theme`.
1923
+ * @throws unsupported (rejects) A player before 0.4.3.
1924
+ */
1925
+ setLanguage(language: LanguagePreference): Promise<void>;
1926
+ }
1927
+ ```
1928
+
1929
+ ### Locale
1930
+
1931
+ An interface language of the player: English (`en`, the source language
1932
+ of every plugin text) or a translation. The list is `LOCALES` in
1933
+ `@vestra-player/sdk/testing`.
1934
+
1935
+ ```ts
1936
+ type Locale = "en" | "zh-Hans" | "ja" | "ko" | "de" | "fr" | "es";
1937
+ ```
1938
+
1939
+ ### LanguagePreference
1940
+
1941
+ The saved interface language (Appearance.language): follow the
1942
+ system, or one Locale.
1943
+
1944
+ ```ts
1945
+ type LanguagePreference = "system" | Locale;
1946
+ ```
1947
+
1948
+ ### TemplateArgs
1949
+
1950
+ Arguments of a template sentence (Osd.show,
1951
+ OsdPanel.set, ConfirmOptions.args): an array for `{}` and
1952
+ `{0}`, `{1}` placeholders, or an object for `{name}` placeholders. At most
1953
+ 16 strings and finite numbers; whole numbers print without a fraction.
1954
+
1955
+ ```ts
1956
+ type TemplateArgs = readonly (string | number)[] | Readonly<Record<string, string | number>>;
1957
+ ```
1958
+
1959
+ ### I18n
1960
+
1961
+ The interface language of this run. `plugin.i18n`.
1962
+
1963
+ - @capability None.
1964
+
1965
+ ```ts
1966
+ interface I18n {
1967
+ /**
1968
+ * The interface language of this run, chosen at start-up from the saved Appearance.language or the system's languages; English when neither names a Locale. Players before 0.4.3 report `"en"`.
1969
+ */
1970
+ readonly locale: Locale;
1904
1971
  }
1905
1972
  ```
1906
1973
 
@@ -2279,6 +2346,10 @@ interface ConfirmOptions {
2279
2346
  destructive?: boolean;
2280
2347
  /** A checkbox under the text, such as "Don't ask again"; 1–40 characters. */
2281
2348
  checkbox?: string;
2349
+ /**
2350
+ * Values for the placeholders of `message` and `detail` (player 0.4.3). Every text of the dialog is English source text that the player shows translated from the plugin's catalog; `message` and `detail` are templates like Osd.show's. The length limits apply to the shown text.
2351
+ */
2352
+ args?: TemplateArgs;
2282
2353
  }
2283
2354
  ```
2284
2355
 
@@ -2901,9 +2972,10 @@ interface OsdPanel extends Disposable {
2901
2972
  /**
2902
2973
  * Show the block with this text, change it, or hide it.
2903
2974
  * @capability ui.osd
2904
- * @param text - The text, with `\n` between lines: at most 32 lines and 4096 bytes. `null` hides the block.
2975
+ * @param text - The text, with `\n` between lines: at most 32 lines and 4096 bytes once translated and formatted. `null` hides the block. A template, like Osd.show's.
2976
+ * @param args - Values for its placeholders (player 0.4.3).
2905
2977
  */
2906
- set(text: string | null): void;
2978
+ set(text: string | null, args?: TemplateArgs): void;
2907
2979
  }
2908
2980
  ```
2909
2981
 
@@ -2919,13 +2991,14 @@ interface Osd {
2919
2991
  /**
2920
2992
  * Show a one-line message at the top left of the video for 2 seconds.
2921
2993
  * @capability ui.osd
2922
- * @param text - 1–200 characters; line breaks become spaces.
2994
+ * @param text - The template, 1–200 characters once translated and formatted; line breaks become spaces.
2995
+ * @param args - Values for its placeholders (player 0.4.3; older players show the template as it is).
2923
2996
  * @returns Resolves once the message is on screen.
2924
- * @throws invalid (rejects) `text` is empty, only white space, or longer than 200 characters.
2997
+ * @throws invalid (rejects) The shown text is empty, only white space, or longer than 200 characters, or `args` is not a TemplateArgs.
2925
2998
  * @throws unavailable (rejects) The player runs without a user interface.
2926
2999
  * @throws capability (rejects) The manifest does not declare `ui.osd`.
2927
3000
  */
2928
- show(text: string): Promise<void>;
3001
+ show(text: string, args?: TemplateArgs): Promise<void>;
2929
3002
  /**
2930
3003
  * Create this plugin's information block.
2931
3004
  * @capability ui.osd
@@ -3634,6 +3707,28 @@ interface PluginInfo {
3634
3707
  * What a running plugin was refused because another plugin holds it: a panel position (`slot-occupied`, with `slot`) or the next/previous sequence (`sequence-taken`). Neither fails activation; `error` has the same as text. Empty from players before 0.4.x (after 0.4.0). Always set by the SDK; optional so that code building `PluginInfo` values itself (tests) keeps compiling.
3635
3708
  */
3636
3709
  warnings?: readonly PluginWarning[];
3710
+ /**
3711
+ * The manifest's display texts in the interface language, translated with that package's catalog; absent from players before 0.4.3 (read `manifest.json` there).
3712
+ */
3713
+ display?: PluginDisplay;
3714
+ }
3715
+ ```
3716
+
3717
+ ### PluginDisplay
3718
+
3719
+ PluginInfo.display: a package's `name`, `punchline`,
3720
+ `description` and `tags` as the player shows them.
3721
+
3722
+ ```ts
3723
+ interface PluginDisplay {
3724
+ /** `name`, translated; `null` when the manifest has none. */
3725
+ readonly name: string | null;
3726
+ /** `punchline`, translated; `null` when the manifest has none. */
3727
+ readonly punchline: string | null;
3728
+ /** `description`, translated; `null` when the manifest has none. */
3729
+ readonly description: string | null;
3730
+ /** `tags`, each translated. */
3731
+ readonly tags: readonly string[];
3637
3732
  }
3638
3733
  ```
3639
3734
 
@@ -4244,6 +4339,15 @@ TestHost.coreAction (the player's core actions).
4244
4339
  interface FakeCommand {
4245
4340
  /** What the plugin declared: id, menu path, title, order, group, default bindings, focus and repeat. Wire-level shape; it may gain fields in minor versions. */
4246
4341
  declaration: CommandDeclaration;
4342
+ /**
4343
+ * What the menu shows, as the player shows it in the test host's TestHost.locale: the title and each path segment translated with the plugin's catalog (the player's own menus in the player's words), the keys normalized (`@video` is "Video").
4344
+ */
4345
+ readonly display: {
4346
+ /** The menu text of the item. */
4347
+ title: string;
4348
+ /** Each segment of the path as shown; empty without a path. */
4349
+ path: string[];
4350
+ };
4247
4351
  /** The command state the player last received (after the update pass). */
4248
4352
  state: {
4249
4353
  /** `false` when the plugin disabled the command. */
@@ -4381,10 +4485,62 @@ class TestHost implements Transport {
4381
4485
  */
4382
4486
  sidebar: LayoutSlot | null;
4383
4487
  };
4384
- /** Every `osd.show` text, newest last. */
4488
+ /**
4489
+ * Every `osd.show` text as the player shows it (translated with the plugin's catalog and formatted with its args), newest last. The template and args the plugin sent are in `host.sent("osd.show")`.
4490
+ */
4385
4491
  readonly osd: string[];
4386
- /** The text of this plugin's osd panel, `null` when hidden. */
4492
+ /** The text of this plugin's osd panel as shown, `null` when hidden. */
4387
4493
  osdPanel: string | null;
4494
+ /**
4495
+ * The interface language the test host plays (`plugin.i18n.locale`), `"en"`
4496
+ * by default. Set it before startPlugin (or pass its `locale`
4497
+ * option): the plugin's catalog for it is read from the package's
4498
+ * `locales/` then, and the menus (FakeCommand.display), OSD
4499
+ * messages, dialog texts, settings page and panel titles show its
4500
+ * translations, the way the player shows them.
4501
+ *
4502
+ * The player's own texts (core actions' messages and menus, its
4503
+ * refusals) are English, or the player's Simplified Chinese for
4504
+ * `"zh-Hans"`. While `locale` was never set they stay in Chinese, as
4505
+ * players up to 0.4.2 showed them, so tests written for SDK 1.5.8 keep
4506
+ * passing; set `locale` to test what a user sees now.
4507
+ */
4508
+ get locale(): Locale;
4509
+ set locale(locale: Locale);
4510
+ /**
4511
+ * Read the plugin's translations for TestHost.locale from a package directory, as the player would (`locales/<locale>.json` of a built package, else the source `locales/<locale>.po`); `startPlugin` does this. None for English; a missing file means none; a broken `.po` throws.
4512
+ * @param directory - The package directory (holding `locales/`).
4513
+ */
4514
+ loadCatalog(directory: string): void;
4515
+ /**
4516
+ * A text of the plugin as the player shows it: translated with the plugin's catalog (TestHost.loadCatalog), then formatted with `args` when given.
4517
+ * @param text - English source text or template.
4518
+ * @param args - Values for its placeholders.
4519
+ * @returns The shown text.
4520
+ */
4521
+ translate(text: string, args?: TemplateArgs): string;
4522
+ /** The saved interface language (`plugin.appearance.language`), changed by `appearance.setLanguage` or TestHost.setLanguage. */
4523
+ language: LanguagePreference;
4524
+ /**
4525
+ * Simulate another plugin (or the user) saving the interface language: pushes `appearance.changed`.
4526
+ * @param language - `"system"` or a locale.
4527
+ */
4528
+ setLanguage(language: LanguagePreference): void;
4529
+ /**
4530
+ * Every `dialogs.confirm` as the player shows it: its texts translated (message and detail formatted with `args`), newest last. The options the plugin sent are in TestHost.confirms.
4531
+ */
4532
+ readonly confirmTexts: {
4533
+ /** The question. */
4534
+ message: string;
4535
+ /** The text under it, if any. */
4536
+ detail?: string;
4537
+ /** The confirm button. */
4538
+ confirm: string;
4539
+ /** The cancel button (the player's "Cancel" by default). */
4540
+ cancel: string;
4541
+ /** The checkbox, if any. */
4542
+ checkbox?: string;
4543
+ }[];
4388
4544
  /** The sequence provider's flags, `null` without a provider. */
4389
4545
  sequence: {
4390
4546
  /** The provider's `canNext`, as last evaluated. */
@@ -5042,6 +5198,10 @@ function startPlugin(activate: (plugin: Plugin) => unknown, options?: {
5042
5198
  host?: TestHost;
5043
5199
  /** Use an existing directory as `plugin.dataDirectory` (to test a restart). */
5044
5200
  dataDirectory?: string;
5201
+ /**
5202
+ * The interface language to play (TestHost.locale): the plugin's `locales/<locale>.po` (or `.json`) in `directory` is loaded and its menus, messages and titles show translated. Default: the host's.
5203
+ */
5204
+ locale?: Locale;
5045
5205
  }): Promise<{
5046
5206
  /** The test host the plugin talks to. */
5047
5207
  host: TestHost;
@@ -5087,6 +5247,16 @@ agrees with the player's.
5087
5247
  const MENU_IDS: readonly ["open", "playback", "subtitles", "video", "audio", "screen", "settings"];
5088
5248
  ```
5089
5249
 
5250
+ ### LOCALES
5251
+
5252
+ The player's interface languages: English (the source language) first,
5253
+ then the translations, as the player's own table (its tests check that
5254
+ they agree).
5255
+
5256
+ ```ts
5257
+ const LOCALES: readonly ["en", "zh-Hans", "ja", "ko", "de", "fr", "es"];
5258
+ ```
5259
+
5090
5260
  ### HostError
5091
5261
 
5092
5262
  A definite refusal from the player (or the plugin runner), with a stable
@@ -5221,6 +5391,8 @@ interface Transport {
5221
5391
  on(event: string, listener: (data: unknown) => void): () => void;
5222
5392
  /** Replaces the real local services (test host). */
5223
5393
  local?: LocalServices;
5394
+ /** The interface language of this run (`plugin.i18n.locale`): the runner passes the player's `VESTRA_LOCALE`, the test host its `locale`. */
5395
+ locale?: string;
5224
5396
  }
5225
5397
  ```
5226
5398
 
@@ -7124,3 +7296,171 @@ test("the window shows sample.md, with the package as its base", async () => {
7124
7296
  await stop();
7125
7297
  });
7126
7298
  ```
7299
+
7300
+ ### Example: i18n
7301
+
7302
+ A plugin in the player's language: **Tools → Counter…** opens a window with one button that counts clicks. Every text is English in the source; `locales/zh-Hans.po` translates it into Simplified Chinese, and the player shows the translation when its interface language is Simplified Chinese.
7303
+
7304
+ | Capabilities | API |
7305
+ | --- | --- |
7306
+ | `commands`, `ui.window`, `ui.osd` | `commands.add`, `windows.create`, `osd.show(template, args)`; Slint `@tr`; `vestra-plugin i18n extract` and `check`; tests with `startPlugin({ locale })` |
7307
+
7308
+ #### Walkthrough
7309
+
7310
+ A plugin in the player's language. **Tools ▸ Counter…** opens a window with one button; each click counts and shows the count on the video. All texts are English in the source, and `locales/zh-Hans.po` translates them: with the player in Simplified Chinese the menu reads **工具 ▸ 计数器…** and the window **2 次点击**. The plugin contains no translation code.
7311
+
7312
+ #### The code
7313
+
7314
+ (code: `main.ts`, below)
7315
+
7316
+ - The menu path is English. The player shows each segment translated with the plugin's catalog; menus of all plugins merge by these English words.
7317
+ - The window gets data (`count`), not text.
7318
+ - plugin.osd.show takes a template and its arguments. The player translates `"Count: {}"`, then puts `count` into `{}`. A sentence built in TypeScript (`` `Count: ${count}` ``) could not be translated.
7319
+
7320
+ #### The window
7321
+
7322
+ (code: `ui/counter.slint`, below)
7323
+
7324
+ Every text is in `@tr("…")`. The player replaces the English literal with the translation when it loads the file; Slint then fills in the arguments. `"{n} click" | "{n} clicks" % root.count` has two forms: the first for 1, the second for every other number (`{n}` is the number).
7325
+
7326
+ #### The translation
7327
+
7328
+ `vestra-plugin i18n extract` collected the texts into `locales/messages.pot`; `extract --locale zh-Hans` started `locales/zh-Hans.po`, and each `msgstr` was then filled in:
7329
+
7330
+ (code: `locales/zh-Hans.po`, below)
7331
+
7332
+ Chinese has one plural form, so `msgstr[0]` is enough. `vestra-plugin i18n check` reports what is missing; `vestra-plugin build` compiles the file into `dist/locales/zh-Hans.json`, which the player reads.
7333
+
7334
+ #### The test
7335
+
7336
+ (code: `tests/main.test.ts`, below)
7337
+
7338
+ `startPlugin(activate, { directory, locale })` loads the catalog of that language. `host.command(id).display` is the menu as the user sees it, `declaration` what the code declared; `host.osd` lists the messages as shown.
7339
+
7340
+ #### examples/i18n/manifest.json
7341
+
7342
+ ```json
7343
+ {
7344
+ "id": "examples.i18n",
7345
+ "version": "0.1.0",
7346
+ "apiVersion": "vestra/1",
7347
+ "entry": "main.ts",
7348
+ "minPlayerVersion": "0.4.3",
7349
+ "capabilities": ["commands", "ui.window", "ui.osd"],
7350
+ "name": "Counter",
7351
+ "author": "Vestra Player SDK examples",
7352
+ "punchline": "A click counter in the player's language",
7353
+ "description": "Tools ▸ Counter… opens a window with one button that counts clicks.\nSDK example: a plugin translated into Simplified Chinese.",
7354
+ "tags": ["example"]
7355
+ }
7356
+ ```
7357
+
7358
+ #### examples/i18n/main.ts
7359
+
7360
+ ```ts
7361
+ /**
7362
+ * Counter: Tools ▸ Counter… opens a window with one button that counts
7363
+ * clicks, in the player's language. The texts are English; the player
7364
+ * translates them with locales/<lang>.po. Walkthrough: docs/examples/i18n.md.
7365
+ */
7366
+ import type { Plugin } from "@vestra-player/sdk";
7367
+
7368
+ export default function activate(plugin: Plugin) {
7369
+ let count = 0;
7370
+
7371
+ // The window's texts are in its Slint file (@tr); TypeScript passes data.
7372
+ const window = plugin.windows.create("ui/counter.slint");
7373
+ window.bind(() => ({ count }));
7374
+ window.on("add", () => {
7375
+ count += 1;
7376
+ // A template and its arguments, never a sentence built in TypeScript.
7377
+ return plugin.osd.show("Count: {}", [count]);
7378
+ });
7379
+
7380
+ // Declared texts are English; the player shows them translated.
7381
+ plugin.commands.add({
7382
+ id: "counter",
7383
+ path: ["Tools", "Counter…"],
7384
+ run: () => window.show(),
7385
+ });
7386
+ }
7387
+ ```
7388
+
7389
+ #### examples/i18n/ui/counter.slint
7390
+
7391
+ ```slint
7392
+ // The Counter window. Every text is @tr("English"): the player puts the
7393
+ // translation in its place when it loads the file. Walkthrough:
7394
+ // docs/examples/i18n.md.
7395
+ import { Theme } from "@vestra/theme.slint";
7396
+ import { Button } from "@vestra/widgets.slint";
7397
+
7398
+ export component CounterWindow inherits Window {
7399
+ title: @tr("Counter");
7400
+ width: 240px;
7401
+ background: Theme.bg-panel;
7402
+
7403
+ in property <int> count;
7404
+ callback add();
7405
+
7406
+ VerticalLayout {
7407
+ padding: Theme.dialog-padding;
7408
+ spacing: 8px;
7409
+ Text {
7410
+ // One form for 1, the other for every other number.
7411
+ text: @tr("{n} click" | "{n} clicks" % root.count);
7412
+ color: Theme.text;
7413
+ font-size: Theme.font-body;
7414
+ }
7415
+ Button {
7416
+ text: @tr("Add one");
7417
+ clicked => { root.add(); }
7418
+ }
7419
+ }
7420
+ }
7421
+ ```
7422
+
7423
+ #### examples/i18n/tests/main.test.ts
7424
+
7425
+ ```ts
7426
+ import assert from "node:assert/strict";
7427
+ import test from "node:test";
7428
+ import { startPlugin } from "@vestra-player/sdk/testing";
7429
+ import activate from "../main.ts";
7430
+
7431
+ const directory = new URL("..", import.meta.url); // manifest.json and locales/
7432
+
7433
+ test("English: the texts as written", async () => {
7434
+ const { host, stop } = await startPlugin(activate, {
7435
+ directory,
7436
+ locale: "en",
7437
+ });
7438
+ const counter = host.command("counter");
7439
+ assert.deepEqual(counter.display.path, ["Tools", "Counter…"]);
7440
+ await counter.run();
7441
+ host.window("counter").emit("add");
7442
+ await host.settle();
7443
+ assert.deepEqual(host.osd, ["Count: 1"]);
7444
+ await stop();
7445
+ });
7446
+
7447
+ test("Simplified Chinese: the menu and the message from locales/zh-Hans.po", async () => {
7448
+ const { host, plugin, stop } = await startPlugin(activate, {
7449
+ directory,
7450
+ locale: "zh-Hans",
7451
+ });
7452
+ assert.equal(plugin.i18n.locale, "zh-Hans");
7453
+ const counter = host.command("counter");
7454
+ // The key stays English; the menu shows the translation.
7455
+ assert.deepEqual(counter.declaration.path, ["Tools", "Counter…"]);
7456
+ assert.deepEqual(counter.display.path, ["工具", "计数器…"]);
7457
+ await counter.run();
7458
+ const window = host.window("counter");
7459
+ window.emit("add");
7460
+ window.emit("add");
7461
+ await host.settle();
7462
+ assert.equal(window.props["count"], 2);
7463
+ assert.deepEqual(host.osd, ["计数:1", "计数:2"]);
7464
+ await stop();
7465
+ });
7466
+ ```
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.5.8: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
3
+ > @vestra-player/sdk 1.5.9: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls; it runs in its own Node.js process. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
4
4
 
5
5
  **How plugins run**
6
6
 
@@ -120,6 +120,8 @@ test("shows the position", async () => {
120
120
  - `vestra-plugin test [dir...]`: Runs `node --test` on `tests/*.test.{ts,mjs}`.
121
121
  - `vestra-plugin build [dir...] [--version x.y.z]`: Writes `dist/`: types stripped, files copied, manifest versioned.
122
122
  - `vestra-plugin pack [dir...] [--version x.y.z] [--out dir]`: Builds, then packs `dist/` as `<id>-<version>.vxp`.
123
+ - `vestra-plugin i18n extract [dir...] [--locale lang]`: Collects the texts to translate into `locales/`, keeping translations.
124
+ - `vestra-plugin i18n check [dir...]`: Translation coverage and untranslatable text (warnings only).
123
125
  - `vestra-player --plugin <dir>` runs a plugin from source, no build needed.
124
126
 
125
127
  **Error codes** (`error.code`; branch on it, never on `message`)
@@ -150,54 +152,56 @@ test("shows the position", async () => {
150
152
  - [Concept: Windows, panels, settings pages, dialogs and the OSD](https://vestrasys.github.io/vestra-player-sdk/documents/Windows__panels__settings_pages__dialogs_and_the_OSD.html): L281–626
151
153
  - [How-to: Keep a slider responsive while dragging](https://vestrasys.github.io/vestra-player-sdk/documents/Keep_a_slider_responsive_while_dragging.html): L627–801
152
154
  - [How-to: Remember a window's state between runs](https://vestrasys.github.io/vestra-player-sdk/documents/Remember_a_window_s_state_between_runs.html): L802–931
153
- - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4173
154
- - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4174–5120
155
+ - [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4268
156
+ - [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4269–5290
155
157
 
156
158
  ## API
157
159
 
158
160
  Where each module of `plugin` is declared:
159
161
 
160
- - Plugin L1151, Scope L1022, PluginError L974, Disposable L947
161
- - plugin.commands: Commands L1347
162
- - plugin.windows: Windows L1552
163
- - plugin.panels: Panels L2003
164
- - plugin.dialogs: Dialogs L2285
165
- - plugin.player: Player L2724
166
- - plugin.video: VideoFilter L2973, VideoView L3036, RenderOptions L3078
167
- - plugin.audio: AudioFilter L2997
168
- - plugin.subtitles: Subtitles L3176
169
- - plugin.media: MediaCapture L3334
170
- - plugin.resolvers: Resolvers L3439
171
- - plugin.packages: Packages L3657
172
- - plugin.osd: Osd L2910
173
- - plugin.host: Host L2873
174
- - plugin.clipboard: Clipboard L3770
175
- - plugin.power: Power L3790
176
- - plugin.tools: Tools L3823
177
- - plugin.storage: Storage L3866
178
- - plugin.system: System L3889
179
- - plugin.secrets: Secrets L3907
180
- - plugin.window: MainWindow L2126
181
- - plugin.app: App L2207
182
- - plugin.bindings: Bindings L3740
183
- - plugin.ui: Ui L1439
184
- - plugin.timeline: Timeline L2083
185
- - plugin.drops: Drops L1849
186
- - plugin.appearance: Appearance L1878
187
- - plugin.settings: Settings L1936
162
+ - Plugin L1153, Scope L1022, PluginError L974, Disposable L947
163
+ - plugin.commands: Commands L1356
164
+ - plugin.windows: Windows L1561
165
+ - plugin.panels: Panels L2070
166
+ - plugin.dialogs: Dialogs L2356
167
+ - plugin.player: Player L2795
168
+ - plugin.video: VideoFilter L3046, VideoView L3109, RenderOptions L3151
169
+ - plugin.audio: AudioFilter L3070
170
+ - plugin.subtitles: Subtitles L3249
171
+ - plugin.media: MediaCapture L3407
172
+ - plugin.resolvers: Resolvers L3512
173
+ - plugin.packages: Packages L3752
174
+ - plugin.osd: Osd L2982
175
+ - plugin.host: Host L2944
176
+ - plugin.clipboard: Clipboard L3865
177
+ - plugin.power: Power L3885
178
+ - plugin.tools: Tools L3918
179
+ - plugin.storage: Storage L3961
180
+ - plugin.system: System L3984
181
+ - plugin.secrets: Secrets L4002
182
+ - plugin.window: MainWindow L2193
183
+ - plugin.app: App L2274
184
+ - plugin.bindings: Bindings L3835
185
+ - plugin.ui: Ui L1448
186
+ - plugin.timeline: Timeline L2150
187
+ - plugin.drops: Drops L1858
188
+ - plugin.appearance: Appearance L1887
189
+ - plugin.i18n: I18n L1959
190
+ - plugin.settings: Settings L2003
188
191
 
189
192
  ## Examples
190
193
 
191
194
  Complete plugins with tests, in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples) and in llms-full.txt:
192
195
 
193
- - hello: The README Quick start: one menu command and an on-screen message. L5534–5623
194
- - blur: A tool window with one slider that blurs the playing video. L5624–5885
195
- - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L5886–6218
196
- - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6219–6403
197
- - panel-menu: A sidebar panel with its own right-click menu. L6404–6609
198
- - menus: Radio menu items, separator groups and a menu item whose title changes. L6610–6769
199
- - theme-widgets: A tool window drawn with the player's theme and widgets. L6770–6933
200
- - markdown: A window that shows a Markdown document. L6934–7127
196
+ - hello: The README Quick start: one menu command and an on-screen message. L5706–5795
197
+ - blur: A tool window with one slider that blurs the playing video. L5796–6057
198
+ - signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L6058–6390
199
+ - bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6391–6575
200
+ - panel-menu: A sidebar panel with its own right-click menu. L6576–6781
201
+ - menus: Radio menu items, separator groups and a menu item whose title changes. L6782–6941
202
+ - theme-widgets: A tool window drawn with the player's theme and widgets. L6942–7105
203
+ - markdown: A window that shows a Markdown document. L7106–7299
204
+ - i18n: A plugin in the player's language: English source text, a Chinese translation. L7300–7467
201
205
 
202
206
  ## Optional
203
207
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.5.8",
3
+ "version": "1.5.9",
4
4
  "description": "Plugin API types, test host and plugin CLI for Vestra Player (apiVersion vestra/1)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -7,7 +7,7 @@
7
7
  // `MarkdownView` scrolls one document. `MarkdownBlocks` does not scroll and
8
8
  // is as tall as its content: put it in your own layout or in a `ListView`
9
9
  // row (a chat message: a `[MarkdownBlock]` field of the row's struct).
10
- // `code-copy: true` adds a 复制 button to code blocks that reports
10
+ // `code-copy: true` adds a Copy button to code blocks that reports
11
11
  // `copy-code(code)`; the plugin copies it (`plugin.clipboard`).
12
12
  import { ScrollView } from "std-widgets.slint";
13
13
  import { Theme } from "theme.slint";
@@ -139,7 +139,7 @@ component MarkdownBlockView {
139
139
  border-radius: Theme.radius-control;
140
140
  background: copy-touch.pressed ? Theme.button-press : copy-touch.has-hover ? Theme.button-hover : Theme.button-bg;
141
141
  copy-label := Text {
142
- text: "复制";
142
+ text: @tr("Copy");
143
143
  color: copy-touch.has-hover ? Theme.text : Theme.text-dim;
144
144
  font-size: Theme.font-body;
145
145
  vertical-alignment: center;
@@ -160,7 +160,7 @@ component MarkdownBlockView {
160
160
  }
161
161
  }
162
162
  if block.kind == "image" && !block.has-image: Text {
163
- text: block.alt == "" ? "[图片]" : "[图片] " + block.alt;
163
+ text: block.alt == "" ? @tr("[Image]") : @tr("[Image] {}", block.alt);
164
164
  color: Theme.text-dim;
165
165
  font-size: Theme.font-body;
166
166
  wrap: word-wrap;
@@ -214,7 +214,7 @@ component MarkdownBlockView {
214
214
  // Markdown blocks without scrolling, as tall as their content.
215
215
  export component MarkdownBlocks inherits Rectangle {
216
216
  in property <[MarkdownBlock]> blocks;
217
- // A 复制 button on code blocks, reporting `copy-code(code)`.
217
+ // A Copy button on code blocks, reporting `copy-code(code)`.
218
218
  in property <bool> code-copy: false;
219
219
  callback link-clicked(string);
220
220
  callback copy-code(string);
@@ -239,7 +239,7 @@ export component MarkdownView inherits Rectangle {
239
239
  in property <[MarkdownBlock]> blocks;
240
240
  // Space around the document.
241
241
  in property <length> inset: 10px;
242
- // A 复制 button on code blocks, reporting `copy-code(code)`.
242
+ // A Copy button on code blocks, reporting `copy-code(code)`.
243
243
  in property <bool> code-copy: false;
244
244
  callback link-clicked(string);
245
245
  callback copy-code(string);