@vestra-player/sdk 1.5.8 → 1.5.11
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 +39 -0
- package/README.md +4 -1
- package/bin/vestra-plugin.mjs +170 -7
- package/dist/commands.d.ts +7 -4
- package/dist/i18n-tools.d.ts +67 -0
- package/dist/i18n-tools.js +787 -0
- package/dist/i18n.d.ts +222 -0
- package/dist/i18n.js +972 -0
- package/dist/player-runtime.d.ts +2 -0
- package/dist/player-runtime.js +2 -0
- package/dist/plugin/appearance.js +27 -1
- package/dist/plugin/bindings.js +1 -0
- package/dist/plugin/commands.js +24 -4
- package/dist/plugin/index.js +9 -1
- package/dist/plugin/language.d.ts +8 -0
- package/dist/plugin/language.js +79 -0
- package/dist/plugin/osd.d.ts +11 -0
- package/dist/plugin/osd.js +64 -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/subtitles.js +9 -1
- package/dist/plugin/testing.d.ts +184 -5
- package/dist/plugin/testing.js +459 -67
- package/dist/plugin/timeline.js +3 -1
- package/dist/plugin/types.d.ts +523 -54
- package/dist/plugin/ui.js +29 -10
- package/dist/plugin/values.d.ts +55 -0
- package/dist/plugin/values.js +117 -0
- package/dist/plugin/window.d.ts +2 -0
- package/dist/plugin/window.js +4 -0
- package/dist/source-resolvers.d.ts +0 -1
- package/dist/source-resolvers.js +9 -1
- package/dist/subtitles.d.ts +3 -1
- package/dist/wire.d.ts +38 -6
- package/dist/wire.js +13 -2
- package/llms-full.txt +646 -58
- package/llms.txt +44 -40
- 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.11: 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
|
|
|
@@ -998,11 +998,11 @@ change).
|
|
|
998
998
|
| `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
|
|
999
999
|
| `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
|
|
1000
1000
|
| `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
|
|
1001
|
-
| `limit` | A player-wide limit was reached (for example the number of commands). |
|
|
1001
|
+
| `limit` | A player-wide limit was reached (for example the number of commands), a property value is over the limits of PropValue, or a request is longer than the player's 1 MiB message. |
|
|
1002
1002
|
| `reserved` | Packages.validate, PackageCandidate.install: the id is `vestra` or starts with `vestra.` (any case), which only the player's built-in plugins use (player 0.4.1). |
|
|
1003
1003
|
| `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
|
|
1004
1004
|
| `ui-prepare` | The player could not prepare a mounted panel or a settings page (its Slint file does not compile, a bound property is not declared, or another plugin's panel holds the slot); `message` has the diagnostics. |
|
|
1005
|
-
| `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. |
|
|
1005
|
+
| `ui` | The player could not apply a property to a tool window or a settings page, or could not build the Options window (a property the Slint file does not declare, a value of the wrong type); `message` has Slint's reason. Also reported to Scope.onError without a request: a callback whose arguments the player could not convert, a property it could not show again when it rebuilt a view (an image file removed since). |
|
|
1006
1006
|
| `host` | Any other failure inside the player; `message` is the player's reason. |
|
|
1007
1007
|
| `disabled` | The plugin is being stopped; pending requests fail with it. |
|
|
1008
1008
|
| `unknown`, `rejected`, `expired` | After a transport problem the SDK could not confirm the outcome (does not happen in normal operation). |
|
|
@@ -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. */
|
|
@@ -1199,8 +1201,8 @@ interface CommandState {
|
|
|
1199
1201
|
checked: boolean | null;
|
|
1200
1202
|
/** Shown as busy; like `enabled: false`, the player does not call `run`. Default false. */
|
|
1201
1203
|
busy: boolean;
|
|
1202
|
-
/** Shown when the command is disabled or busy. */
|
|
1203
|
-
reason:
|
|
1204
|
+
/** Shown when the command is disabled or busy: English source text the player translates, or a Text (SDK 1.5.11). */
|
|
1205
|
+
reason: TextValue | null;
|
|
1204
1206
|
}
|
|
1205
1207
|
```
|
|
1206
1208
|
|
|
@@ -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,17 +1265,29 @@ 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
|
-
*
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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.
|
|
1282
|
+
*
|
|
1283
|
+
* A segment that mixes words with data (a track number, a count) is a
|
|
1284
|
+
* Text (SDK 1.5.11, player 0.4.3): `plugin.i18n.text("Track {}
|
|
1285
|
+
* ({})", [n, codec])`. The menu shows it translated and filled in; it
|
|
1286
|
+
* merges by its English rendering ("Track 2 (opus)").
|
|
1287
|
+
*/
|
|
1288
|
+
path?: (string | Text)[];
|
|
1289
|
+
/** Menu label (English source text, shown translated, or a Text); defaults to the last path segment. */
|
|
1290
|
+
title?: string | Text;
|
|
1277
1291
|
/**
|
|
1278
1292
|
* 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.
|
|
1279
1293
|
*/
|
|
@@ -1315,8 +1329,8 @@ interface CommandOptions {
|
|
|
1315
1329
|
checked?: Bindable<boolean | null>;
|
|
1316
1330
|
/** Busy state (default false); the player does not call `run` while busy. */
|
|
1317
1331
|
busy?: Bindable<boolean>;
|
|
1318
|
-
/** Why the command is disabled or busy (shown to the user). */
|
|
1319
|
-
reason?: Bindable<
|
|
1332
|
+
/** Why the command is disabled or busy (shown to the user): English source text, or a Text for one with data in it (SDK 1.5.11). */
|
|
1333
|
+
reason?: Bindable<TextValue | null>;
|
|
1320
1334
|
/**
|
|
1321
1335
|
* Run the command (menu item, binding, panel menu or `host.command(id).run()` in tests). It may return a promise; errors and rejections go to Scope.onError, and bound values are re-evaluated when it returns and when its promise settles.
|
|
1322
1336
|
*/
|
|
@@ -1423,7 +1437,7 @@ JSON whose values may also be Markdown documents, at any depth: the part
|
|
|
1423
1437
|
of PropValue that nests.
|
|
1424
1438
|
|
|
1425
1439
|
```ts
|
|
1426
|
-
type PropData = null | boolean | number | string | MarkdownRef | PropData[] | {
|
|
1440
|
+
type PropData = null | boolean | number | string | MarkdownRef | Text | TextList | PropData[] | {
|
|
1427
1441
|
[key: string]: PropData;
|
|
1428
1442
|
};
|
|
1429
1443
|
```
|
|
@@ -1901,6 +1915,196 @@ interface Appearance {
|
|
|
1901
1915
|
* @throws capability (rejects) The manifest does not declare `ui.theme`.
|
|
1902
1916
|
*/
|
|
1903
1917
|
set(theme: ThemeId): Promise<void>;
|
|
1918
|
+
/** The saved interface language: `"system"` (follow the system's languages, the default) or a Locale. */
|
|
1919
|
+
readonly language: LanguagePreference;
|
|
1920
|
+
/**
|
|
1921
|
+
* Save the interface language for the next start.
|
|
1922
|
+
* @capability ui.theme
|
|
1923
|
+
* @player 0.4.3
|
|
1924
|
+
* @param language - `"system"` or a Locale.
|
|
1925
|
+
* @returns Resolves once the player saved it; Appearance.language has the new value.
|
|
1926
|
+
* @throws invalid (rejects) `language` is not `"system"` or a Locale.
|
|
1927
|
+
* @throws capability (rejects) The manifest does not declare `ui.theme`.
|
|
1928
|
+
* @throws unsupported (rejects) A player before 0.4.3.
|
|
1929
|
+
*/
|
|
1930
|
+
setLanguage(language: LanguagePreference): Promise<void>;
|
|
1931
|
+
}
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
### Locale
|
|
1935
|
+
|
|
1936
|
+
An interface language of the player: English (`en`, the source language
|
|
1937
|
+
of every plugin text) or a translation. The list is `LOCALES` in
|
|
1938
|
+
`@vestra-player/sdk/testing`.
|
|
1939
|
+
|
|
1940
|
+
```ts
|
|
1941
|
+
type Locale = "en" | "zh-Hans" | "ja" | "ko" | "de" | "fr" | "es";
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
### LanguagePreference
|
|
1945
|
+
|
|
1946
|
+
The saved interface language (Appearance.language): follow the
|
|
1947
|
+
system, or one Locale.
|
|
1948
|
+
|
|
1949
|
+
```ts
|
|
1950
|
+
type LanguagePreference = "system" | Locale;
|
|
1951
|
+
```
|
|
1952
|
+
|
|
1953
|
+
### TemplateArgs
|
|
1954
|
+
|
|
1955
|
+
Arguments of a template sentence (Osd.show,
|
|
1956
|
+
OsdPanel.set, ConfirmOptions.args, I18n.text): an
|
|
1957
|
+
array for `{}` and `{0}`, `{1}` placeholders, or an object for `{name}`
|
|
1958
|
+
placeholders. At most 16. Strings and finite numbers are data, shown as
|
|
1959
|
+
they are (whole numbers without a fraction); a Text or
|
|
1960
|
+
TextList argument (SDK 1.5.11, player 0.4.3) is translated too,
|
|
1961
|
+
so a sentence can be built from translated parts.
|
|
1962
|
+
|
|
1963
|
+
```ts
|
|
1964
|
+
type TemplateArgs = readonly TextArg[] | Readonly<Record<string, TextArg>>;
|
|
1965
|
+
```
|
|
1966
|
+
|
|
1967
|
+
### Text
|
|
1968
|
+
|
|
1969
|
+
A text the player translates with the plugin's catalog and fills in:
|
|
1970
|
+
English source text with its arguments. Made by I18n.text; plain
|
|
1971
|
+
data, not a translation (the plugin never sees the translated string).
|
|
1972
|
+
|
|
1973
|
+
```ts
|
|
1974
|
+
interface Text {
|
|
1975
|
+
/** The English source text: a template with `{}`, `{0}` or `{name}`. */
|
|
1976
|
+
readonly $vestraText: string;
|
|
1977
|
+
/** Values for its placeholders. */
|
|
1978
|
+
readonly args?: TemplateArgs;
|
|
1979
|
+
}
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
### TextList
|
|
1983
|
+
|
|
1984
|
+
Values joined the way the interface language joins them ("a, b" in
|
|
1985
|
+
English, "a、b" in Chinese). Made by I18n.list; accepted where a
|
|
1986
|
+
Text is.
|
|
1987
|
+
|
|
1988
|
+
```ts
|
|
1989
|
+
interface TextList {
|
|
1990
|
+
/** The items: data, or texts the player translates. */
|
|
1991
|
+
readonly $vestraList: readonly TextArg[];
|
|
1992
|
+
/** How they are joined; `"list"` by default. */
|
|
1993
|
+
readonly style?: ListStyle;
|
|
1994
|
+
}
|
|
1995
|
+
```
|
|
1996
|
+
|
|
1997
|
+
### ListStyle
|
|
1998
|
+
|
|
1999
|
+
How a TextList is joined, per interface language:
|
|
2000
|
+
|
|
2001
|
+
| style | English | 简体中文 | 日本語 |
|
|
2002
|
+
| --- | --- | --- | --- |
|
|
2003
|
+
| `list` (enumeration) | `, ` | `、` | `、` |
|
|
2004
|
+
| `clauses` (parts of one sentence) | `, ` | `,` | `、` |
|
|
2005
|
+
| `semicolons` | `; ` | `;` | `;` |
|
|
2006
|
+
| `sentences` (no final full stop) | `. ` | `。` | `。` |
|
|
2007
|
+
| `lines` | line break | line break | line break |
|
|
2008
|
+
|
|
2009
|
+
Korean, German and Spanish join as English; French puts a narrow space
|
|
2010
|
+
before `;`.
|
|
2011
|
+
|
|
2012
|
+
```ts
|
|
2013
|
+
type ListStyle = "list" | "clauses" | "semicolons" | "sentences" | "lines";
|
|
2014
|
+
```
|
|
2015
|
+
|
|
2016
|
+
### TextArg
|
|
2017
|
+
|
|
2018
|
+
An argument of a template: data (a string or a finite number, shown as it
|
|
2019
|
+
is) or a Text or TextList (translated).
|
|
2020
|
+
|
|
2021
|
+
```ts
|
|
2022
|
+
type TextArg = string | number | Text | TextList;
|
|
2023
|
+
```
|
|
2024
|
+
|
|
2025
|
+
### TextValue
|
|
2026
|
+
|
|
2027
|
+
A text the player shows: English source text (translated as a whole), a
|
|
2028
|
+
Text or a TextList.
|
|
2029
|
+
|
|
2030
|
+
```ts
|
|
2031
|
+
type TextValue = string | Text | TextList;
|
|
2032
|
+
```
|
|
2033
|
+
|
|
2034
|
+
### I18n
|
|
2035
|
+
|
|
2036
|
+
The interface language of this run. `plugin.i18n`.
|
|
2037
|
+
|
|
2038
|
+
- @capability None.
|
|
2039
|
+
|
|
2040
|
+
```ts
|
|
2041
|
+
interface I18n {
|
|
2042
|
+
/**
|
|
2043
|
+
* 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"`.
|
|
2044
|
+
*/
|
|
2045
|
+
readonly locale: Locale;
|
|
2046
|
+
/**
|
|
2047
|
+
* A text that mixes words with data, for the player to translate: the
|
|
2048
|
+
* English source text (a template) and its arguments. It returns a plain
|
|
2049
|
+
* Text value, not a translation; pass it where the player shows
|
|
2050
|
+
* text (see Text) and the player shows it translated with this
|
|
2051
|
+
* plugin's catalog. `vestra-plugin i18n extract` collects `source` from
|
|
2052
|
+
* every call, so write it as a literal (or a conditional of literals, or
|
|
2053
|
+
* an `as const` table entry).
|
|
2054
|
+
*
|
|
2055
|
+
* Arguments are data unless they are texts themselves, so a sentence can
|
|
2056
|
+
* be built from translated parts without composing it in TypeScript.
|
|
2057
|
+
* `text` does not use `this`: `const { text, list } = plugin.i18n` works.
|
|
2058
|
+
* @param source - English source text; `{}`, `{0}` or `{name}` for the arguments, `{{` and `}}` for braces.
|
|
2059
|
+
* @param args - Values for its placeholders.
|
|
2060
|
+
* @throws invalid A malformed or over-limit value (`TEXT_LIMITS`).
|
|
2061
|
+
* @capability None.
|
|
2062
|
+
*/
|
|
2063
|
+
text(source: string, args?: TemplateArgs): Text;
|
|
2064
|
+
/**
|
|
2065
|
+
* Items joined the way the interface language joins them (see ListStyle): `list(["a.glsl", "b.glsl"])` shows "a.glsl, b.glsl" in English and "a.glsl、b.glsl" in Chinese. Items are data, or texts the player translates. Accepted where a Text is.
|
|
2066
|
+
* @param items - At most 256.
|
|
2067
|
+
* @param style - `"list"` by default.
|
|
2068
|
+
* @throws invalid A malformed or over-limit value.
|
|
2069
|
+
* @capability None.
|
|
2070
|
+
*/
|
|
2071
|
+
list(items: readonly TextArg[], style?: ListStyle): TextList;
|
|
2072
|
+
/**
|
|
2073
|
+
* An `Error` whose words the player shows translated: throw it where the player shows a plugin's failure to the user, such as from a file or web resolver's `resolve` (FileResolverOptions, WebResolverOptions) (also when a stream fails while it starts). The player shows `text` translated with this plugin's catalog (as `player.state.error` and the failure message); `message` is the English rendering, for logs and older players. Elsewhere it is an ordinary `Error`.
|
|
2074
|
+
* @param source - English source text, as I18n.text's.
|
|
2075
|
+
* @param args - Values for its placeholders.
|
|
2076
|
+
* @throws invalid A malformed or over-limit value.
|
|
2077
|
+
* @capability None.
|
|
2078
|
+
*/
|
|
2079
|
+
error(source: string, args?: TemplateArgs): TextError;
|
|
2080
|
+
/**
|
|
2081
|
+
* The string a text shows in this run's language, for text that leaves
|
|
2082
|
+
* the player: what the plugin copies to the clipboard or writes to a file
|
|
2083
|
+
* ("Copy info", "Save as…"). Translated with this plugin's catalog (a
|
|
2084
|
+
* string as a whole, a Text or TextList with its
|
|
2085
|
+
* arguments), the way the player would show it.
|
|
2086
|
+
*
|
|
2087
|
+
* Only for text leaving the player. Never use it for the interface (a
|
|
2088
|
+
* window, the menu, the OSD, a dialog): pass the value itself there, so
|
|
2089
|
+
* the player translates it and `vestra-plugin i18n check` can see the
|
|
2090
|
+
* sentence is whole.
|
|
2091
|
+
* @param value - Source text, or a text value.
|
|
2092
|
+
* @returns The translated string.
|
|
2093
|
+
* @throws invalid A malformed value.
|
|
2094
|
+
* @capability None.
|
|
2095
|
+
*/
|
|
2096
|
+
resolve(value: TextValue): string;
|
|
2097
|
+
}
|
|
2098
|
+
```
|
|
2099
|
+
|
|
2100
|
+
### TextError
|
|
2101
|
+
|
|
2102
|
+
An `Error` the player shows translated (I18n.error).
|
|
2103
|
+
|
|
2104
|
+
```ts
|
|
2105
|
+
interface TextError extends Error {
|
|
2106
|
+
/** What the player shows. */
|
|
2107
|
+
readonly text: Text;
|
|
1904
2108
|
}
|
|
1905
2109
|
```
|
|
1906
2110
|
|
|
@@ -2068,12 +2272,12 @@ This plugin's own marks; disposing it removes them.
|
|
|
2068
2272
|
```ts
|
|
2069
2273
|
interface TimelineLayer extends Disposable {
|
|
2070
2274
|
/**
|
|
2071
|
-
* Replace the layer (last write wins; `[]` clears it). At most 1000 marks, titles ≤ 100 characters, times within the media. Failures go to `onError`; with `ifCurrent`, a write for another source is dropped silently.
|
|
2275
|
+
* Replace the layer (last write wins; `[]` clears it). At most 1000 marks, titles ≤ 100 characters, times within the media. Failures go to `onError`; with `ifCurrent`, a write for another source is dropped silently. A title is data (a chapter's or bookmark's own name), or a Text the player translates (`text("Chapter {}", [n])`, SDK 1.5.11); other plugins read the shown string.
|
|
2072
2276
|
*/
|
|
2073
2277
|
set(marks: {
|
|
2074
2278
|
time: number;
|
|
2075
2279
|
kind: MarkKind;
|
|
2076
|
-
title?: string;
|
|
2280
|
+
title?: string | Text;
|
|
2077
2281
|
}[], options?: {
|
|
2078
2282
|
ifCurrent?: PlayerState;
|
|
2079
2283
|
}): void;
|
|
@@ -2268,17 +2472,21 @@ Chinese character is one character).
|
|
|
2268
2472
|
```ts
|
|
2269
2473
|
interface ConfirmOptions {
|
|
2270
2474
|
/** The question, shown in bold; 1–200 characters. */
|
|
2271
|
-
message:
|
|
2475
|
+
message: TextValue;
|
|
2272
2476
|
/** More text under the message; at most 2000 characters. */
|
|
2273
|
-
detail?:
|
|
2477
|
+
detail?: TextValue;
|
|
2274
2478
|
/** The confirm button's text (the default button: Enter picks it); 1–40 characters. */
|
|
2275
|
-
confirm:
|
|
2479
|
+
confirm: TextValue;
|
|
2276
2480
|
/** The cancel button's text (Escape picks it); 1–40 characters. */
|
|
2277
|
-
cancel?:
|
|
2481
|
+
cancel?: TextValue;
|
|
2278
2482
|
/** Mark the confirm button as destructive: red text on macOS, a warning icon on Windows. */
|
|
2279
2483
|
destructive?: boolean;
|
|
2280
2484
|
/** A checkbox under the text, such as "Don't ask again"; 1–40 characters. */
|
|
2281
|
-
checkbox?:
|
|
2485
|
+
checkbox?: TextValue;
|
|
2486
|
+
/**
|
|
2487
|
+
* Values for the placeholders of `message` and `detail` (player 0.4.3). Any of the texts may instead be a Text (SDK 1.5.11), which carries its own arguments; `args` only fills the string ones. 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.
|
|
2488
|
+
*/
|
|
2489
|
+
args?: TemplateArgs;
|
|
2282
2490
|
}
|
|
2283
2491
|
```
|
|
2284
2492
|
|
|
@@ -2319,8 +2527,8 @@ interface Dialogs {
|
|
|
2319
2527
|
* @throws capability (rejects) The manifest does not declare `ui.dialog`.
|
|
2320
2528
|
*/
|
|
2321
2529
|
openFile(options?: {
|
|
2322
|
-
/** The dialog's title, at most 256 bytes. */
|
|
2323
|
-
title?:
|
|
2530
|
+
/** The dialog's title (English source text or a Text), at most 256 bytes. */
|
|
2531
|
+
title?: TextValue;
|
|
2324
2532
|
}): Promise<string | null>;
|
|
2325
2533
|
/**
|
|
2326
2534
|
* Ask the user for one or more files to open.
|
|
@@ -2333,8 +2541,8 @@ interface Dialogs {
|
|
|
2333
2541
|
* @throws capability (rejects) The manifest does not declare `ui.dialog`.
|
|
2334
2542
|
*/
|
|
2335
2543
|
openFiles(options?: {
|
|
2336
|
-
/** The dialog's title, at most 256 bytes. */
|
|
2337
|
-
title?:
|
|
2544
|
+
/** The dialog's title (English source text or a Text), at most 256 bytes. */
|
|
2545
|
+
title?: TextValue;
|
|
2338
2546
|
}): Promise<string[]>;
|
|
2339
2547
|
/**
|
|
2340
2548
|
* Ask the user for a folder.
|
|
@@ -2347,8 +2555,8 @@ interface Dialogs {
|
|
|
2347
2555
|
* @throws capability (rejects) The manifest does not declare `ui.dialog`.
|
|
2348
2556
|
*/
|
|
2349
2557
|
openDirectory(options?: {
|
|
2350
|
-
/** The dialog's title, at most 256 bytes. */
|
|
2351
|
-
title?:
|
|
2558
|
+
/** The dialog's title (English source text or a Text), at most 256 bytes. */
|
|
2559
|
+
title?: TextValue;
|
|
2352
2560
|
}): Promise<string | null>;
|
|
2353
2561
|
/**
|
|
2354
2562
|
* Ask the user where to save a file.
|
|
@@ -2361,10 +2569,12 @@ interface Dialogs {
|
|
|
2361
2569
|
* @throws capability (rejects) The manifest does not declare `ui.dialog`.
|
|
2362
2570
|
*/
|
|
2363
2571
|
saveFile(options?: {
|
|
2364
|
-
/** The dialog's title, at most 256 bytes. */
|
|
2365
|
-
title?:
|
|
2366
|
-
/**
|
|
2367
|
-
|
|
2572
|
+
/** The dialog's title (English source text or a Text), at most 256 bytes. */
|
|
2573
|
+
title?: TextValue;
|
|
2574
|
+
/**
|
|
2575
|
+
* The proposed file name: a plain name, at most 256 bytes. A string is data (not translated); a Text (SDK 1.5.11) is shown translated, `text("Playlist.m3u8")` (a `/` or `\\` in a translation becomes `-`).
|
|
2576
|
+
*/
|
|
2577
|
+
fileName?: TextValue;
|
|
2368
2578
|
}): Promise<string | null>;
|
|
2369
2579
|
}
|
|
2370
2580
|
```
|
|
@@ -2901,9 +3111,24 @@ interface OsdPanel extends Disposable {
|
|
|
2901
3111
|
/**
|
|
2902
3112
|
* Show the block with this text, change it, or hide it.
|
|
2903
3113
|
* @capability ui.osd
|
|
2904
|
-
* @param text - The text, with `\n` between lines: at most 32 lines and 4096 bytes. `null` hides the block.
|
|
3114
|
+
* @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.
|
|
3115
|
+
* @param args - Values for its placeholders (player 0.4.3); `null` or omitted for none.
|
|
3116
|
+
* @param options - `maxColumns`: cut long lines (SDK 1.5.11).
|
|
3117
|
+
*/
|
|
3118
|
+
set(text: TextValue | null, args?: TemplateArgs | null, options?: OsdPanelOptions): void;
|
|
3119
|
+
}
|
|
3120
|
+
```
|
|
3121
|
+
|
|
3122
|
+
### OsdPanelOptions
|
|
3123
|
+
|
|
3124
|
+
Options of OsdPanel.set.
|
|
3125
|
+
|
|
3126
|
+
```ts
|
|
3127
|
+
interface OsdPanelOptions {
|
|
3128
|
+
/**
|
|
3129
|
+
* Cut every line longer than this many columns, after translation and formatting, ending it with `…`: a wide character (Chinese, Japanese, Korean, full-width forms) is two columns, a combining mark none, any other one. An integer from 8 to 1000 (player 0.4.3; older players show whole lines). Measure the video area to choose it.
|
|
2905
3130
|
*/
|
|
2906
|
-
|
|
3131
|
+
maxColumns?: number;
|
|
2907
3132
|
}
|
|
2908
3133
|
```
|
|
2909
3134
|
|
|
@@ -2919,13 +3144,14 @@ interface Osd {
|
|
|
2919
3144
|
/**
|
|
2920
3145
|
* Show a one-line message at the top left of the video for 2 seconds.
|
|
2921
3146
|
* @capability ui.osd
|
|
2922
|
-
* @param text - 1–200 characters; line breaks become spaces.
|
|
3147
|
+
* @param text - The template, 1–200 characters once translated and formatted; line breaks become spaces.
|
|
3148
|
+
* @param args - Values for its placeholders (player 0.4.3; older players show the template as it is).
|
|
2923
3149
|
* @returns Resolves once the message is on screen.
|
|
2924
|
-
* @throws invalid (rejects)
|
|
3150
|
+
* @throws invalid (rejects) The shown text is empty, only white space, or longer than 200 characters, or `args` is not a TemplateArgs.
|
|
2925
3151
|
* @throws unavailable (rejects) The player runs without a user interface.
|
|
2926
3152
|
* @throws capability (rejects) The manifest does not declare `ui.osd`.
|
|
2927
3153
|
*/
|
|
2928
|
-
show(text:
|
|
3154
|
+
show(text: TextValue, args?: TemplateArgs): Promise<void>;
|
|
2929
3155
|
/**
|
|
2930
3156
|
* Create this plugin's information block.
|
|
2931
3157
|
* @capability ui.osd
|
|
@@ -3195,7 +3421,9 @@ interface Subtitles {
|
|
|
3195
3421
|
/**
|
|
3196
3422
|
* Upload a subtitle file's bytes (UTF-8 SRT, or ASS/SSA with
|
|
3197
3423
|
* `format: "ass"`) for the current source; select it unless
|
|
3198
|
-
* `select: "none"`. `title` (1–200 characters) names the track
|
|
3424
|
+
* `select: "none"`. `title` (1–200 characters) names the track: data, or
|
|
3425
|
+
* a Text the player translates (`text("{} · {}", [text("Simplified
|
|
3426
|
+
* Chinese"), file])`, SDK 1.5.11). Aborting
|
|
3199
3427
|
* `signal` before the commit discards the upload (rejects with AbortError);
|
|
3200
3428
|
* once the commit was sent it completes.
|
|
3201
3429
|
*
|
|
@@ -3205,7 +3433,7 @@ interface Subtitles {
|
|
|
3205
3433
|
*/
|
|
3206
3434
|
load(bytes: Uint8Array, options?: SubtitleConditional & {
|
|
3207
3435
|
format?: "srt" | "ass";
|
|
3208
|
-
title?: string;
|
|
3436
|
+
title?: string | Text;
|
|
3209
3437
|
select?: "primary" | "secondary" | "none";
|
|
3210
3438
|
signal?: AbortSignal;
|
|
3211
3439
|
}): Promise<SubtitleTrack>;
|
|
@@ -3634,6 +3862,28 @@ interface PluginInfo {
|
|
|
3634
3862
|
* 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
3863
|
*/
|
|
3636
3864
|
warnings?: readonly PluginWarning[];
|
|
3865
|
+
/**
|
|
3866
|
+
* 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).
|
|
3867
|
+
*/
|
|
3868
|
+
display?: PluginDisplay;
|
|
3869
|
+
}
|
|
3870
|
+
```
|
|
3871
|
+
|
|
3872
|
+
### PluginDisplay
|
|
3873
|
+
|
|
3874
|
+
PluginInfo.display: a package's `name`, `punchline`,
|
|
3875
|
+
`description` and `tags` as the player shows them.
|
|
3876
|
+
|
|
3877
|
+
```ts
|
|
3878
|
+
interface PluginDisplay {
|
|
3879
|
+
/** `name`, translated; `null` when the manifest has none. */
|
|
3880
|
+
readonly name: string | null;
|
|
3881
|
+
/** `punchline`, translated; `null` when the manifest has none. */
|
|
3882
|
+
readonly punchline: string | null;
|
|
3883
|
+
/** `description`, translated; `null` when the manifest has none. */
|
|
3884
|
+
readonly description: string | null;
|
|
3885
|
+
/** `tags`, each translated. */
|
|
3886
|
+
readonly tags: readonly string[];
|
|
3637
3887
|
}
|
|
3638
3888
|
```
|
|
3639
3889
|
|
|
@@ -3715,8 +3965,12 @@ One command's bindings, from Bindings.list.
|
|
|
3715
3965
|
interface BindingInfo extends BindingRef {
|
|
3716
3966
|
/** Menu title. */
|
|
3717
3967
|
title: string;
|
|
3718
|
-
/** Menu path; `null` for a command outside the menu. */
|
|
3968
|
+
/** Menu path as shown; `null` for a command outside the menu. */
|
|
3719
3969
|
path: string[] | null;
|
|
3970
|
+
/**
|
|
3971
|
+
* The menu path's keys: the English text menus merge by, the same in every interface language (`["Playback", "Seek", "Back 5 s"]`; a built-in menu by its key, `"Video"`, also when declared as `@video`). `null` for a command outside the menu, and from players before 0.4.3. Find a command by where it is in the menu with this, not with `path`. Always set by `list()`; optional so that test fakes written for older SDKs still type-check.
|
|
3972
|
+
*/
|
|
3973
|
+
key?: string[] | null;
|
|
3720
3974
|
/** Global, or only while the plugin's panel has focus. */
|
|
3721
3975
|
scope: "global" | "panel";
|
|
3722
3976
|
/** The command's own bindings. */
|
|
@@ -4192,8 +4446,9 @@ and its semantics are simplified:
|
|
|
4192
4446
|
value ranges (an out-of-range `filter.set` ends in `failed` with
|
|
4193
4447
|
`invalid`), an empty chain removes the plugin's chain, a chain on a
|
|
4194
4448
|
generated stream fails `unsupported` (the player's own conformance
|
|
4195
|
-
cases check all three), and the command limits (COMMAND_LIMITS)
|
|
4196
|
-
|
|
4449
|
+
cases check all three), and the command limits (COMMAND_LIMITS),
|
|
4450
|
+
property value limits (UI_VALUE_LIMITS) and menu ids
|
|
4451
|
+
(MENU_IDS) are enforced.
|
|
4197
4452
|
|
|
4198
4453
|
Typical workflow: start the plugin with startPlugin (it reads the
|
|
4199
4454
|
plugin's `manifest.json`), drive it the way the user or another plugin
|
|
@@ -4234,6 +4489,21 @@ interface FakeWindow {
|
|
|
4234
4489
|
}
|
|
4235
4490
|
```
|
|
4236
4491
|
|
|
4492
|
+
### FileDialogText
|
|
4493
|
+
|
|
4494
|
+
A file dialog as the player shows it (TestHost.fileDialogs).
|
|
4495
|
+
|
|
4496
|
+
```ts
|
|
4497
|
+
interface FileDialogText {
|
|
4498
|
+
/** Which dialog. */
|
|
4499
|
+
kind: "openFile" | "openFiles" | "openDirectory" | "saveFile";
|
|
4500
|
+
/** Its title as shown, if the plugin gave one. */
|
|
4501
|
+
title?: string;
|
|
4502
|
+
/** The proposed file name as shown, if the plugin gave one. */
|
|
4503
|
+
fileName?: string;
|
|
4504
|
+
}
|
|
4505
|
+
```
|
|
4506
|
+
|
|
4237
4507
|
### FakeCommand
|
|
4238
4508
|
|
|
4239
4509
|
A command the plugin registered with `plugin.commands.add`, as the test
|
|
@@ -4244,6 +4514,19 @@ TestHost.coreAction (the player's core actions).
|
|
|
4244
4514
|
interface FakeCommand {
|
|
4245
4515
|
/** 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
4516
|
declaration: CommandDeclaration;
|
|
4517
|
+
/**
|
|
4518
|
+
* 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").
|
|
4519
|
+
*/
|
|
4520
|
+
readonly display: {
|
|
4521
|
+
/** The menu text of the item. */
|
|
4522
|
+
title: string;
|
|
4523
|
+
/** Each segment of the path as shown; empty without a path. */
|
|
4524
|
+
path: string[];
|
|
4525
|
+
/**
|
|
4526
|
+
* The disabled or busy reason as shown, `null` without one (a plugin command's; not enumerable, so comparing `display` with `{ title, path }` still works).
|
|
4527
|
+
*/
|
|
4528
|
+
reason?: string | null;
|
|
4529
|
+
};
|
|
4247
4530
|
/** The command state the player last received (after the update pass). */
|
|
4248
4531
|
state: {
|
|
4249
4532
|
/** `false` when the plugin disabled the command. */
|
|
@@ -4252,8 +4535,8 @@ interface FakeCommand {
|
|
|
4252
4535
|
checked: boolean | null;
|
|
4253
4536
|
/** `true` while the plugin reports the command as busy. */
|
|
4254
4537
|
busy: boolean;
|
|
4255
|
-
/** The reason
|
|
4256
|
-
reason:
|
|
4538
|
+
/** The reason the plugin sent (source text or a text value; see `display.reason`). */
|
|
4539
|
+
reason: TextValue | null;
|
|
4257
4540
|
};
|
|
4258
4541
|
/** The state revision the player holds; increases with every state update the SDK sends. */
|
|
4259
4542
|
revision: number;
|
|
@@ -4381,10 +4664,66 @@ class TestHost implements Transport {
|
|
|
4381
4664
|
*/
|
|
4382
4665
|
sidebar: LayoutSlot | null;
|
|
4383
4666
|
};
|
|
4384
|
-
/**
|
|
4667
|
+
/**
|
|
4668
|
+
* 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")`.
|
|
4669
|
+
*/
|
|
4385
4670
|
readonly osd: string[];
|
|
4386
|
-
/** The text of this plugin's osd panel, `null` when hidden. */
|
|
4671
|
+
/** The text of this plugin's osd panel as shown, `null` when hidden. */
|
|
4387
4672
|
osdPanel: string | null;
|
|
4673
|
+
/**
|
|
4674
|
+
* The interface language the test host plays (`plugin.i18n.locale`), `"en"`
|
|
4675
|
+
* by default. Set it before startPlugin (or pass its `locale`
|
|
4676
|
+
* option): the plugin's catalog for it is read from the package's
|
|
4677
|
+
* `locales/` then, and the menus (FakeCommand.display), OSD
|
|
4678
|
+
* messages, dialog texts, settings page and panel titles show its
|
|
4679
|
+
* translations, the way the player shows them.
|
|
4680
|
+
*
|
|
4681
|
+
* The player's own texts (core actions' messages and menus, its
|
|
4682
|
+
* refusals) are English, or the player's Simplified Chinese for
|
|
4683
|
+
* `"zh-Hans"`. While `locale` was never set they stay in Chinese, as
|
|
4684
|
+
* players up to 0.4.2 showed them, so tests written for SDK 1.5.8 keep
|
|
4685
|
+
* passing; set `locale` to test what a user sees now.
|
|
4686
|
+
*/
|
|
4687
|
+
get locale(): Locale;
|
|
4688
|
+
set locale(locale: Locale);
|
|
4689
|
+
/**
|
|
4690
|
+
* 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.
|
|
4691
|
+
* @param directory - The package directory (holding `locales/`).
|
|
4692
|
+
*/
|
|
4693
|
+
loadCatalog(directory: string): void;
|
|
4694
|
+
/**
|
|
4695
|
+
* A text of the plugin as the player shows it: translated with the plugin's catalog (TestHost.loadCatalog), then formatted with `args` when given.
|
|
4696
|
+
* @param text - English source text or template.
|
|
4697
|
+
* @param args - Values for its placeholders.
|
|
4698
|
+
* @returns The shown text.
|
|
4699
|
+
*/
|
|
4700
|
+
translate(text: TextValue, args?: TemplateArgs): string;
|
|
4701
|
+
/** The saved interface language (`plugin.appearance.language`), changed by `appearance.setLanguage` or TestHost.setLanguage. */
|
|
4702
|
+
language: LanguagePreference;
|
|
4703
|
+
/**
|
|
4704
|
+
* Simulate another plugin (or the user) saving the interface language: pushes `appearance.changed`.
|
|
4705
|
+
* @param language - `"system"` or a locale.
|
|
4706
|
+
*/
|
|
4707
|
+
setLanguage(language: LanguagePreference): void;
|
|
4708
|
+
/**
|
|
4709
|
+
* 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.
|
|
4710
|
+
*/
|
|
4711
|
+
readonly confirmTexts: {
|
|
4712
|
+
/** The question. */
|
|
4713
|
+
message: string;
|
|
4714
|
+
/** The text under it, if any. */
|
|
4715
|
+
detail?: string;
|
|
4716
|
+
/** The confirm button. */
|
|
4717
|
+
confirm: string;
|
|
4718
|
+
/** The cancel button (the player's "Cancel" by default). */
|
|
4719
|
+
cancel: string;
|
|
4720
|
+
/** The checkbox, if any. */
|
|
4721
|
+
checkbox?: string;
|
|
4722
|
+
}[];
|
|
4723
|
+
/**
|
|
4724
|
+
* Every file dialog (`openFile`, `openFiles`, `openDirectory`, `saveFile`) as the player shows it: the title translated, a text file name translated, newest last. The options the plugin sent are in `host.sent("dialogs.open")`.
|
|
4725
|
+
*/
|
|
4726
|
+
readonly fileDialogs: FileDialogText[];
|
|
4388
4727
|
/** The sequence provider's flags, `null` without a provider. */
|
|
4389
4728
|
sequence: {
|
|
4390
4729
|
/** The provider's `canNext`, as last evaluated. */
|
|
@@ -5042,6 +5381,10 @@ function startPlugin(activate: (plugin: Plugin) => unknown, options?: {
|
|
|
5042
5381
|
host?: TestHost;
|
|
5043
5382
|
/** Use an existing directory as `plugin.dataDirectory` (to test a restart). */
|
|
5044
5383
|
dataDirectory?: string;
|
|
5384
|
+
/**
|
|
5385
|
+
* 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.
|
|
5386
|
+
*/
|
|
5387
|
+
locale?: Locale;
|
|
5045
5388
|
}): Promise<{
|
|
5046
5389
|
/** The test host the plugin talks to. */
|
|
5047
5390
|
host: TestHost;
|
|
@@ -5087,6 +5430,80 @@ agrees with the player's.
|
|
|
5087
5430
|
const MENU_IDS: readonly ["open", "playback", "subtitles", "video", "audio", "screen", "settings"];
|
|
5088
5431
|
```
|
|
5089
5432
|
|
|
5433
|
+
### LOCALES
|
|
5434
|
+
|
|
5435
|
+
The player's interface languages: English (the source language) first,
|
|
5436
|
+
then the translations, as the player's own table (its tests check that
|
|
5437
|
+
they agree).
|
|
5438
|
+
|
|
5439
|
+
```ts
|
|
5440
|
+
const LOCALES: readonly ["en", "zh-Hans", "ja", "ko", "de", "fr", "es"];
|
|
5441
|
+
```
|
|
5442
|
+
|
|
5443
|
+
### LIST_SEPARATORS
|
|
5444
|
+
|
|
5445
|
+
The separator of each list style per interface language, as the player's
|
|
5446
|
+
table (`i18n::text::SEPARATORS`; its tests check that they agree).
|
|
5447
|
+
|
|
5448
|
+
```ts
|
|
5449
|
+
const LIST_SEPARATORS: Readonly<Record<Locale, Readonly<Record<ListStyle, string>>>>;
|
|
5450
|
+
```
|
|
5451
|
+
|
|
5452
|
+
### TEXT_LIMITS
|
|
5453
|
+
|
|
5454
|
+
The player's limits on one text value (a
|
|
5455
|
+
Text or
|
|
5456
|
+
TextList with everything nested in it), as the player's
|
|
5457
|
+
`i18n::text` (its tests check that they agree).
|
|
5458
|
+
|
|
5459
|
+
```ts
|
|
5460
|
+
const TEXT_LIMITS: {
|
|
5461
|
+
/** Nesting depth of texts and lists; the value itself is depth 0 (8). */
|
|
5462
|
+
readonly depth: 8;
|
|
5463
|
+
/** Texts, lists and arguments in all, the value itself included (1024). */
|
|
5464
|
+
readonly nodes: 1024;
|
|
5465
|
+
/** Arguments of one text (16). */
|
|
5466
|
+
readonly args: 16;
|
|
5467
|
+
/** Items of one list (256). */
|
|
5468
|
+
readonly items: 256;
|
|
5469
|
+
/** UTF-8 bytes of a source text or a string argument (4096). */
|
|
5470
|
+
readonly stringBytes: 4096;
|
|
5471
|
+
};
|
|
5472
|
+
```
|
|
5473
|
+
|
|
5474
|
+
### UI_VALUE_LIMITS
|
|
5475
|
+
|
|
5476
|
+
The player's limits on one property value (a `PropValue` as written
|
|
5477
|
+
by `View.set` or `View.bind`, or an initial property of a
|
|
5478
|
+
window, panel or settings page). The player's tests check that these
|
|
5479
|
+
agree with the player; TestHost enforces them the same way.
|
|
5480
|
+
|
|
5481
|
+
A value over a limit is refused whole with code `limit` (the property
|
|
5482
|
+
keeps what it showed) and reported to `Scope.onError`: a write by
|
|
5483
|
+
`View.set` or `View.bind` as described there. As an initial property it
|
|
5484
|
+
makes `Window.show` reject; a panel or settings page opens without it and
|
|
5485
|
+
the failure goes to the plugin's `onError`.
|
|
5486
|
+
|
|
5487
|
+
They bound the work one write costs the player's interface thread; they
|
|
5488
|
+
are not meant as list sizes to aim for. A list that can grow without
|
|
5489
|
+
bound (a playlist, a history) should send the rows near the view and say
|
|
5490
|
+
how many there are, not the whole list.
|
|
5491
|
+
|
|
5492
|
+
```ts
|
|
5493
|
+
const UI_VALUE_LIMITS: {
|
|
5494
|
+
/** Nesting depth; the value itself is depth 0 (8). */
|
|
5495
|
+
readonly depth: 8;
|
|
5496
|
+
/** Values in all, counting every array, object, string, number, boolean and null, the value itself included; a Markdown document is one (16384). */
|
|
5497
|
+
readonly values: 16384;
|
|
5498
|
+
/** Rows of one array (4096). */
|
|
5499
|
+
readonly arrayRows: 4096;
|
|
5500
|
+
/** UTF-8 bytes of one string or object field name (64 KiB). */
|
|
5501
|
+
readonly stringBytes: number;
|
|
5502
|
+
/** UTF-8 bytes of all strings and field names together, the text of the value's Markdown documents included (1 MiB). */
|
|
5503
|
+
readonly textBytes: number;
|
|
5504
|
+
};
|
|
5505
|
+
```
|
|
5506
|
+
|
|
5090
5507
|
### HostError
|
|
5091
5508
|
|
|
5092
5509
|
A definite refusal from the player (or the plugin runner), with a stable
|
|
@@ -5221,6 +5638,8 @@ interface Transport {
|
|
|
5221
5638
|
on(event: string, listener: (data: unknown) => void): () => void;
|
|
5222
5639
|
/** Replaces the real local services (test host). */
|
|
5223
5640
|
local?: LocalServices;
|
|
5641
|
+
/** The interface language of this run (`plugin.i18n.locale`): the runner passes the player's `VESTRA_LOCALE`, the test host its `locale`. */
|
|
5642
|
+
locale?: string;
|
|
5224
5643
|
}
|
|
5225
5644
|
```
|
|
5226
5645
|
|
|
@@ -5481,9 +5900,10 @@ interface WireSystemInfo {
|
|
|
5481
5900
|
|
|
5482
5901
|
```ts
|
|
5483
5902
|
type CommandDeclaration = {
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
path
|
|
5903
|
+
/** Source text, or a text value (player 0.4.3). */
|
|
5904
|
+
title: string | TextJson;
|
|
5905
|
+
/** Complete primary menu path INCLUDING its leaf; empty = not in the main menu. Segments may be text values (player 0.4.3). */
|
|
5906
|
+
path: (string | TextJson)[];
|
|
5487
5907
|
/** Sibling order, ascending; a submenu takes its first command's order (a built-in menu keeps a fixed one). */
|
|
5488
5908
|
order: number;
|
|
5489
5909
|
/** Separator label: adjacent siblings of different groups get a line. */
|
|
@@ -7124,3 +7544,171 @@ test("the window shows sample.md, with the package as its base", async () => {
|
|
|
7124
7544
|
await stop();
|
|
7125
7545
|
});
|
|
7126
7546
|
```
|
|
7547
|
+
|
|
7548
|
+
### Example: i18n
|
|
7549
|
+
|
|
7550
|
+
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.
|
|
7551
|
+
|
|
7552
|
+
| Capabilities | API |
|
|
7553
|
+
| --- | --- |
|
|
7554
|
+
| `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 })` |
|
|
7555
|
+
|
|
7556
|
+
#### Walkthrough
|
|
7557
|
+
|
|
7558
|
+
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.
|
|
7559
|
+
|
|
7560
|
+
#### The code
|
|
7561
|
+
|
|
7562
|
+
(code: `main.ts`, below)
|
|
7563
|
+
|
|
7564
|
+
- 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.
|
|
7565
|
+
- The window gets data (`count`), not text.
|
|
7566
|
+
- 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.
|
|
7567
|
+
|
|
7568
|
+
#### The window
|
|
7569
|
+
|
|
7570
|
+
(code: `ui/counter.slint`, below)
|
|
7571
|
+
|
|
7572
|
+
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).
|
|
7573
|
+
|
|
7574
|
+
#### The translation
|
|
7575
|
+
|
|
7576
|
+
`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:
|
|
7577
|
+
|
|
7578
|
+
(code: `locales/zh-Hans.po`, below)
|
|
7579
|
+
|
|
7580
|
+
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.
|
|
7581
|
+
|
|
7582
|
+
#### The test
|
|
7583
|
+
|
|
7584
|
+
(code: `tests/main.test.ts`, below)
|
|
7585
|
+
|
|
7586
|
+
`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.
|
|
7587
|
+
|
|
7588
|
+
#### examples/i18n/manifest.json
|
|
7589
|
+
|
|
7590
|
+
```json
|
|
7591
|
+
{
|
|
7592
|
+
"id": "examples.i18n",
|
|
7593
|
+
"version": "0.1.0",
|
|
7594
|
+
"apiVersion": "vestra/1",
|
|
7595
|
+
"entry": "main.ts",
|
|
7596
|
+
"minPlayerVersion": "0.4.3",
|
|
7597
|
+
"capabilities": ["commands", "ui.window", "ui.osd"],
|
|
7598
|
+
"name": "Counter",
|
|
7599
|
+
"author": "Vestra Player SDK examples",
|
|
7600
|
+
"punchline": "A click counter in the player's language",
|
|
7601
|
+
"description": "Tools ▸ Counter… opens a window with one button that counts clicks.\nSDK example: a plugin translated into Simplified Chinese.",
|
|
7602
|
+
"tags": ["example"]
|
|
7603
|
+
}
|
|
7604
|
+
```
|
|
7605
|
+
|
|
7606
|
+
#### examples/i18n/main.ts
|
|
7607
|
+
|
|
7608
|
+
```ts
|
|
7609
|
+
/**
|
|
7610
|
+
* Counter: Tools ▸ Counter… opens a window with one button that counts
|
|
7611
|
+
* clicks, in the player's language. The texts are English; the player
|
|
7612
|
+
* translates them with locales/<lang>.po. Walkthrough: docs/examples/i18n.md.
|
|
7613
|
+
*/
|
|
7614
|
+
import type { Plugin } from "@vestra-player/sdk";
|
|
7615
|
+
|
|
7616
|
+
export default function activate(plugin: Plugin) {
|
|
7617
|
+
let count = 0;
|
|
7618
|
+
|
|
7619
|
+
// The window's texts are in its Slint file (@tr); TypeScript passes data.
|
|
7620
|
+
const window = plugin.windows.create("ui/counter.slint");
|
|
7621
|
+
window.bind(() => ({ count }));
|
|
7622
|
+
window.on("add", () => {
|
|
7623
|
+
count += 1;
|
|
7624
|
+
// A template and its arguments, never a sentence built in TypeScript.
|
|
7625
|
+
return plugin.osd.show("Count: {}", [count]);
|
|
7626
|
+
});
|
|
7627
|
+
|
|
7628
|
+
// Declared texts are English; the player shows them translated.
|
|
7629
|
+
plugin.commands.add({
|
|
7630
|
+
id: "counter",
|
|
7631
|
+
path: ["Tools", "Counter…"],
|
|
7632
|
+
run: () => window.show(),
|
|
7633
|
+
});
|
|
7634
|
+
}
|
|
7635
|
+
```
|
|
7636
|
+
|
|
7637
|
+
#### examples/i18n/ui/counter.slint
|
|
7638
|
+
|
|
7639
|
+
```slint
|
|
7640
|
+
// The Counter window. Every text is @tr("English"): the player puts the
|
|
7641
|
+
// translation in its place when it loads the file. Walkthrough:
|
|
7642
|
+
// docs/examples/i18n.md.
|
|
7643
|
+
import { Theme } from "@vestra/theme.slint";
|
|
7644
|
+
import { Button } from "@vestra/widgets.slint";
|
|
7645
|
+
|
|
7646
|
+
export component CounterWindow inherits Window {
|
|
7647
|
+
title: @tr("Counter");
|
|
7648
|
+
width: 240px;
|
|
7649
|
+
background: Theme.bg-panel;
|
|
7650
|
+
|
|
7651
|
+
in property <int> count;
|
|
7652
|
+
callback add();
|
|
7653
|
+
|
|
7654
|
+
VerticalLayout {
|
|
7655
|
+
padding: Theme.dialog-padding;
|
|
7656
|
+
spacing: 8px;
|
|
7657
|
+
Text {
|
|
7658
|
+
// One form for 1, the other for every other number.
|
|
7659
|
+
text: @tr("{n} click" | "{n} clicks" % root.count);
|
|
7660
|
+
color: Theme.text;
|
|
7661
|
+
font-size: Theme.font-body;
|
|
7662
|
+
}
|
|
7663
|
+
Button {
|
|
7664
|
+
text: @tr("Add one");
|
|
7665
|
+
clicked => { root.add(); }
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
```
|
|
7670
|
+
|
|
7671
|
+
#### examples/i18n/tests/main.test.ts
|
|
7672
|
+
|
|
7673
|
+
```ts
|
|
7674
|
+
import assert from "node:assert/strict";
|
|
7675
|
+
import test from "node:test";
|
|
7676
|
+
import { startPlugin } from "@vestra-player/sdk/testing";
|
|
7677
|
+
import activate from "../main.ts";
|
|
7678
|
+
|
|
7679
|
+
const directory = new URL("..", import.meta.url); // manifest.json and locales/
|
|
7680
|
+
|
|
7681
|
+
test("English: the texts as written", async () => {
|
|
7682
|
+
const { host, stop } = await startPlugin(activate, {
|
|
7683
|
+
directory,
|
|
7684
|
+
locale: "en",
|
|
7685
|
+
});
|
|
7686
|
+
const counter = host.command("counter");
|
|
7687
|
+
assert.deepEqual(counter.display.path, ["Tools", "Counter…"]);
|
|
7688
|
+
await counter.run();
|
|
7689
|
+
host.window("counter").emit("add");
|
|
7690
|
+
await host.settle();
|
|
7691
|
+
assert.deepEqual(host.osd, ["Count: 1"]);
|
|
7692
|
+
await stop();
|
|
7693
|
+
});
|
|
7694
|
+
|
|
7695
|
+
test("Simplified Chinese: the menu and the message from locales/zh-Hans.po", async () => {
|
|
7696
|
+
const { host, plugin, stop } = await startPlugin(activate, {
|
|
7697
|
+
directory,
|
|
7698
|
+
locale: "zh-Hans",
|
|
7699
|
+
});
|
|
7700
|
+
assert.equal(plugin.i18n.locale, "zh-Hans");
|
|
7701
|
+
const counter = host.command("counter");
|
|
7702
|
+
// The key stays English; the menu shows the translation.
|
|
7703
|
+
assert.deepEqual(counter.declaration.path, ["Tools", "Counter…"]);
|
|
7704
|
+
assert.deepEqual(counter.display.path, ["工具", "计数器…"]);
|
|
7705
|
+
await counter.run();
|
|
7706
|
+
const window = host.window("counter");
|
|
7707
|
+
window.emit("add");
|
|
7708
|
+
window.emit("add");
|
|
7709
|
+
await host.settle();
|
|
7710
|
+
assert.equal(window.props["count"], 2);
|
|
7711
|
+
assert.deepEqual(host.osd, ["计数:1", "计数:2"]);
|
|
7712
|
+
await stop();
|
|
7713
|
+
});
|
|
7714
|
+
```
|