@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/CHANGELOG.md +13 -0
- package/README.md +4 -1
- package/bin/vestra-plugin.mjs +170 -7
- package/dist/i18n-tools.d.ts +67 -0
- package/dist/i18n-tools.js +731 -0
- package/dist/i18n.d.ts +130 -0
- package/dist/i18n.js +673 -0
- package/dist/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -0
- package/dist/plugin/appearance.js +27 -1
- package/dist/plugin/language.d.ts +4 -0
- package/dist/plugin/language.js +5 -0
- package/dist/plugin/osd.d.ts +3 -0
- package/dist/plugin/osd.js +45 -9
- package/dist/plugin/packages.js +10 -0
- package/dist/plugin/runtime.d.ts +3 -1
- package/dist/plugin/runtime.js +6 -0
- package/dist/plugin/scope.d.ts +1 -0
- package/dist/plugin/scope.js +2 -0
- package/dist/plugin/testing.d.ts +133 -3
- package/dist/plugin/testing.js +289 -47
- package/dist/plugin/types.d.ts +194 -20
- package/dist/plugin/ui.js +4 -1
- package/dist/plugin/window.d.ts +2 -0
- package/dist/plugin/window.js +4 -0
- package/dist/wire.d.ts +26 -2
- package/dist/wire.js +12 -2
- package/llms-full.txt +361 -21
- package/llms.txt +43 -39
- package/package.json +1 -1
- package/slint/vestra/markdown.slint +5 -5
- package/slint/vestra/widgets.slint +4 -4
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
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
|
|
1220
|
-
Subtitles
|
|
1221
|
-
|
|
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
|
|
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
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
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)
|
|
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
|
-
/**
|
|
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.
|
|
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–
|
|
154
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin):
|
|
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
|
|
161
|
-
- plugin.commands: Commands
|
|
162
|
-
- plugin.windows: Windows
|
|
163
|
-
- plugin.panels: Panels
|
|
164
|
-
- plugin.dialogs: Dialogs
|
|
165
|
-
- plugin.player: Player
|
|
166
|
-
- plugin.video: VideoFilter
|
|
167
|
-
- plugin.audio: AudioFilter
|
|
168
|
-
- plugin.subtitles: Subtitles
|
|
169
|
-
- plugin.media: MediaCapture
|
|
170
|
-
- plugin.resolvers: Resolvers
|
|
171
|
-
- plugin.packages: Packages
|
|
172
|
-
- plugin.osd: Osd
|
|
173
|
-
- plugin.host: Host
|
|
174
|
-
- plugin.clipboard: Clipboard
|
|
175
|
-
- plugin.power: Power
|
|
176
|
-
- plugin.tools: Tools
|
|
177
|
-
- plugin.storage: Storage
|
|
178
|
-
- plugin.system: System
|
|
179
|
-
- plugin.secrets: Secrets
|
|
180
|
-
- plugin.window: MainWindow
|
|
181
|
-
- plugin.app: App
|
|
182
|
-
- plugin.bindings: Bindings
|
|
183
|
-
- plugin.ui: Ui
|
|
184
|
-
- plugin.timeline: Timeline
|
|
185
|
-
- plugin.drops: Drops
|
|
186
|
-
- plugin.appearance: Appearance
|
|
187
|
-
- plugin.
|
|
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.
|
|
194
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
195
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
196
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
197
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
198
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
199
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
200
|
-
- markdown: A window that shows a Markdown document.
|
|
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
|
@@ -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
|
|
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 == "" ? "[
|
|
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
|
|
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
|
|
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);
|