@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +4 -1
  3. package/bin/vestra-plugin.mjs +170 -7
  4. package/dist/commands.d.ts +7 -4
  5. package/dist/i18n-tools.d.ts +67 -0
  6. package/dist/i18n-tools.js +787 -0
  7. package/dist/i18n.d.ts +222 -0
  8. package/dist/i18n.js +972 -0
  9. package/dist/player-runtime.d.ts +2 -0
  10. package/dist/player-runtime.js +2 -0
  11. package/dist/plugin/appearance.js +27 -1
  12. package/dist/plugin/bindings.js +1 -0
  13. package/dist/plugin/commands.js +24 -4
  14. package/dist/plugin/index.js +9 -1
  15. package/dist/plugin/language.d.ts +8 -0
  16. package/dist/plugin/language.js +79 -0
  17. package/dist/plugin/osd.d.ts +11 -0
  18. package/dist/plugin/osd.js +64 -9
  19. package/dist/plugin/packages.js +10 -0
  20. package/dist/plugin/runtime.d.ts +3 -1
  21. package/dist/plugin/runtime.js +6 -0
  22. package/dist/plugin/scope.d.ts +1 -0
  23. package/dist/plugin/scope.js +2 -0
  24. package/dist/plugin/subtitles.js +9 -1
  25. package/dist/plugin/testing.d.ts +184 -5
  26. package/dist/plugin/testing.js +459 -67
  27. package/dist/plugin/timeline.js +3 -1
  28. package/dist/plugin/types.d.ts +523 -54
  29. package/dist/plugin/ui.js +29 -10
  30. package/dist/plugin/values.d.ts +55 -0
  31. package/dist/plugin/values.js +117 -0
  32. package/dist/plugin/window.d.ts +2 -0
  33. package/dist/plugin/window.js +4 -0
  34. package/dist/source-resolvers.d.ts +0 -1
  35. package/dist/source-resolvers.js +9 -1
  36. package/dist/subtitles.d.ts +3 -1
  37. package/dist/wire.d.ts +38 -6
  38. package/dist/wire.js +13 -2
  39. package/llms-full.txt +646 -58
  40. package/llms.txt +44 -40
  41. package/package.json +1 -1
  42. package/slint/vestra/markdown.slint +5 -5
  43. package/slint/vestra/widgets.slint +4 -4
@@ -73,11 +73,11 @@ export interface Disposable {
73
73
  * | `timeout` | A time limit ran out (`openStream` start-up, `tools.run`, `system.*` after 10 s). |
74
74
  * | `stream`, `device` | `openStream`: the producer failed / the audio device is not ready. |
75
75
  * | `backend-uncertain` | The player's backend blocked a filter stage; call `retry()`. |
76
- * | `limit` | A player-wide limit was reached (for example the number of commands). |
76
+ * | `limit` | A player-wide limit was reached (for example the number of commands), a property value is over the limits of {@link PropValue}, or a request is longer than the player's 1 MiB message. |
77
77
  * | `reserved` | {@link Packages.validate}, {@link 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). |
78
78
  * | `versionMismatch` | The package needs a newer player (`minPlayerVersion`) or another `apiVersion`. |
79
79
  * | `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. |
80
- * | `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. |
80
+ * | `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 {@link 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). |
81
81
  * | `host` | Any other failure inside the player; `message` is the player's reason. |
82
82
  * | `disabled` | The plugin is being stopped; pending requests fail with it. |
83
83
  * | `unknown`, `rejected`, `expired` | After a transport problem the SDK could not confirm the outcome (does not happen in normal operation). |
@@ -224,8 +224,10 @@ export interface Scope extends Disposable {
224
224
  readonly timeline: Timeline;
225
225
  /** Files dropped on the video area, by extension (`ui.drop`). */
226
226
  readonly drops: Drops;
227
- /** The player's palette. */
227
+ /** The player's palette and the saved interface language. */
228
228
  readonly appearance: Appearance;
229
+ /** The interface language of this run (read-only). */
230
+ readonly i18n: I18n;
229
231
  /** Pages in the player's options window (`ui.settings`). */
230
232
  readonly settings: Settings;
231
233
  /**
@@ -339,8 +341,11 @@ export interface CommandState {
339
341
  checked: boolean | null;
340
342
  /** Shown as busy; like `enabled: false`, the player does not call `run`. Default false. */
341
343
  busy: boolean;
342
- /** Shown when the command is disabled or busy. */
343
- reason: string | null;
344
+ /**
345
+ * Shown when the command is disabled or busy: English source text the
346
+ * player translates, or a {@link Text} (SDK 1.5.11).
347
+ */
348
+ reason: TextValue | null;
344
349
  }
345
350
  /**
346
351
  * A value, or a function the SDK re-evaluates on every update pass (see
@@ -360,9 +365,9 @@ export interface CommandState {
360
365
  export type Bindable<T> = T | (() => T);
361
366
  /**
362
367
  * A built-in top-level menu of the player, named in a command path as
363
- * `@<id>` (see {@link CommandOptions.path}): Open (打开), Playback (播放),
364
- * Subtitles (字幕), Video (视频), Audio (声音), Screen (屏幕), Settings (设置).
365
- * The list is `MENU_IDS` in `@vestra-player/sdk/testing`.
368
+ * `@<id>` or by its English key (see {@link CommandOptions.path}): Open,
369
+ * Playback, Subtitles, Video, Audio, Screen, Settings. The list is
370
+ * `MENU_IDS` in `@vestra-player/sdk/testing`.
366
371
  */
367
372
  export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "screen" | "settings";
368
373
  /**
@@ -379,7 +384,7 @@ export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "sc
379
384
  * ({@link MenuId}) sit at fixed orders, whatever is inside them: Open 110,
380
385
  * Playback 200, Subtitles 300, Video 400, Audio 500, Screen 710, Settings
381
386
  * 830 (player 0.3.0). This holds whether the path names the menu by id
382
- * (`["@video", "Blur"]`) or by its title (`["视频", "Blur"]`), so a plugin's
387
+ * (`["@video", "Blur"]`) or by its key (`["Video", "Blur"]`), so a plugin's
383
388
  * item never moves the player's menu. Directly inside a built-in menu,
384
389
  * `order` 0 (the default) sorts after every other order: an item without
385
390
  * `order` goes at the end, after the player's own items (which all use 100
@@ -402,22 +407,43 @@ export interface CommandOptions {
402
407
  * one segment is a top-level item. Omit it for a command that is not in the
403
408
  * main menu (bindings and panel menus only); `title` is then required.
404
409
  *
405
- * Segments are menu titles, matched exactly. The player's own menus have
406
- * Chinese titles, so `["Video", "Blur"]` would add a second top-level menu
407
- * "Video". To join a built-in menu, start the path with its id, `@` plus a
408
- * {@link MenuId}: `@open`, `@playback`, `@subtitles`, `@video`, `@audio`,
409
- * `@screen` or `@settings`. Only the first segment is read as an id; an
410
- * unknown id, or an id with nothing after it, fails with `invalid`. Needs
411
- * player 0.3.0 (set `"minPlayerVersion": "0.3.0"`).
410
+ * Segments are English source text, and menus merge by them: commands
411
+ * of different plugins with the same leading segments share a submenu.
412
+ * The player shows each segment and the title in the interface language
413
+ * from the declaring plugin's catalog (`locales/<lang>.po`, see the
414
+ * [i18n guide](../../docs/how-to/i18n.md)); the player's own menus
415
+ * (Open, Playback, Subtitles, Video, Audio, Screen, Settings, and
416
+ * Playback ▸ Speed, Playback ▸ Seek) always show the player's words.
417
+ * A built-in menu can also be named by its id, `@` plus a {@link MenuId}:
418
+ * `@open`, `@playback`, `@subtitles`, `@video`, `@audio`, `@screen` or
419
+ * `@settings`. Only the first segment is read as an id; an unknown id, or
420
+ * an id with nothing after it, fails with `invalid`. Players up to 0.4.2
421
+ * titled their menus in Chinese (视频…): there `["Video", "Blur"]` is a
422
+ * menu of its own, so plugins for those players write `@video`; player
423
+ * 0.4.3 still accepts the old Chinese titles of its own menus.
424
+ *
425
+ * A segment that mixes words with data (a track number, a count) is a
426
+ * {@link Text} (SDK 1.5.11, player 0.4.3): `plugin.i18n.text("Track {}
427
+ * ({})", [n, codec])`. The menu shows it translated and filled in; it
428
+ * merges by its English rendering ("Track 2 (opus)").
412
429
  *
413
430
  * @example
414
431
  * ```ts
415
432
  * plugin.commands.add({ id: "blur", path: ["@video", "Blur"], run: toggle });
433
+ * const { text } = plugin.i18n;
434
+ * plugin.commands.add({
435
+ * id: "track-2",
436
+ * path: ["@audio", "Audio Track", text("Track {} ({})", [2, "opus"])],
437
+ * run: select,
438
+ * });
416
439
  * ```
417
440
  */
418
- path?: string[];
419
- /** Menu label; defaults to the last path segment. */
420
- title?: string;
441
+ path?: (string | Text)[];
442
+ /**
443
+ * Menu label (English source text, shown translated, or a {@link Text});
444
+ * defaults to the last path segment.
445
+ */
446
+ title?: string | Text;
421
447
  /**
422
448
  * Sibling order in the menu, ascending (integer, default 0). A submenu sits
423
449
  * where its first command sits, except a built-in menu, which keeps its
@@ -463,8 +489,11 @@ export interface CommandOptions {
463
489
  checked?: Bindable<boolean | null>;
464
490
  /** Busy state (default false); the player does not call `run` while busy. */
465
491
  busy?: Bindable<boolean>;
466
- /** Why the command is disabled or busy (shown to the user). */
467
- reason?: Bindable<string | null>;
492
+ /**
493
+ * Why the command is disabled or busy (shown to the user): English
494
+ * source text, or a {@link Text} for one with data in it (SDK 1.5.11).
495
+ */
496
+ reason?: Bindable<TextValue | null>;
468
497
  /**
469
498
  * Run the command (menu item, binding, panel menu or
470
499
  * `host.command(id).run()` in tests). It may return a promise; errors
@@ -631,15 +660,31 @@ export interface MarkdownOptions {
631
660
  * is refused (Slint treats `_` and `-` as the same, so use camelCase or
632
661
  * kebab-case).
633
662
  *
663
+ * A {@link Text} or {@link TextList} goes where the Slint type is `string`,
664
+ * at any depth (SDK 1.5.11, player 0.4.3): the player shows it translated
665
+ * with the plugin's catalog, so a `[string]` model (a ComboBox) can mix
666
+ * words and data. The limits below count it as the string it becomes.
667
+ *
634
668
  * A {@link MarkdownRef} goes where the Slint type is `[MarkdownBlock]`: the
635
669
  * whole value, or a field at any depth (SDK 1.5.4; older players refuse a
636
670
  * nested one). An {@link ImageRef} only as the whole value: nested in an
637
671
  * array or object it is `invalid`.
638
672
  *
639
- * Limits per value: nesting depth 8, 4096 values in total (a document is
640
- * one), 1024 rows per array, 64 KiB per string and 1 MiB of text in total,
641
- * the text of its Markdown documents included (256 KB per document). A
642
- * value over a limit is a failed write (see {@link View}).
673
+ * Limits per value (`UI_VALUE_LIMITS` in `@vestra-player/sdk/testing`,
674
+ * player 0.4.3; older players: 4096 values and 1024 rows): nesting depth 8,
675
+ * 16384 values in total (every array, object, string, number, boolean and
676
+ * null counts, the value itself included; a document is one), 4096 rows
677
+ * per array, 64 KiB per string or field name and 1 MiB of text in total,
678
+ * field names and the text of its Markdown documents included (256 KB per
679
+ * document). A list of rows with 8 fields is 9 values per row, so about
680
+ * 1800 rows fit. The whole request must also fit the player's 1 MiB
681
+ * message. A value over a limit is refused with code `limit` and the
682
+ * property keeps what it showed (see {@link View.set}); `TestHost` refuses
683
+ * it the same way.
684
+ *
685
+ * The limits bound the work of one write, not the size of your data: for
686
+ * a list that can grow without bound, send the rows near the view and the
687
+ * row count, and move that window as the user scrolls.
643
688
  *
644
689
  * @since 1.0.0
645
690
  * @category UI
@@ -659,7 +704,7 @@ export type PropValue = PropData | ImageRef;
659
704
  * @since 1.5.4
660
705
  * @category UI
661
706
  */
662
- export type PropData = null | boolean | number | string | MarkdownRef | PropData[] | {
707
+ export type PropData = null | boolean | number | string | MarkdownRef | Text | TextList | PropData[] | {
663
708
  [key: string]: PropData;
664
709
  };
665
710
  /**
@@ -902,7 +947,13 @@ export interface View {
902
947
  * the next update pass replaces the set value with the bound one.
903
948
  *
904
949
  * Never throws. A write that fails is retried with back-off and reported
905
- * to {@link Scope.onError}.
950
+ * to {@link Scope.onError} (the first failure of a run of failures; the
951
+ * message names the property). A value over the limits of
952
+ * {@link PropValue} fails with `limit`. As an initial property (written
953
+ * while the view is not open) it makes {@link Window.show} reject; a
954
+ * panel or settings page still opens without it (the property keeps its
955
+ * default) and the failure goes to the plugin's `onError` (player 0.4.3;
956
+ * older players left it out silently).
906
957
  *
907
958
  * @capability That of the view: `ui.window`, `ui.mount` or `ui.settings`.
908
959
  *
@@ -935,7 +986,9 @@ export interface View {
935
986
  * run. A view can have several bindings; when two return the same name,
936
987
  * the one bound later wins.
937
988
  *
938
- * Never throws. Writes that fail are not repeated until the value changes.
989
+ * Never throws. Writes that fail are not repeated until the value
990
+ * changes, and are reported to {@link Scope.onError} like a failed
991
+ * {@link View.set} (a value over the limits of {@link PropValue}: `limit`).
939
992
  *
940
993
  * @capability That of the view: `ui.window`, `ui.mount` or `ui.settings`.
941
994
  *
@@ -1940,6 +1993,330 @@ export interface Appearance {
1940
1993
  * @since 1.0.0
1941
1994
  */
1942
1995
  set(theme: ThemeId): Promise<void>;
1996
+ /**
1997
+ * The saved interface language: `"system"` (follow the system's
1998
+ * languages, the default) or a {@link Locale}.
1999
+ *
2000
+ * @remarks
2001
+ * This is the choice for the next start. The language of this run is
2002
+ * {@link I18n.locale} and never changes while the player runs. Read before
2003
+ * activation; with `events` it follows other plugins' changes, without it
2004
+ * only this plugin's own. Players before 0.4.3 report `"system"`.
2005
+ *
2006
+ * @since 1.5.9
2007
+ */
2008
+ readonly language: LanguagePreference;
2009
+ /**
2010
+ * Save the interface language for the next start.
2011
+ *
2012
+ * @remarks
2013
+ * The player keeps the choice in its interface state; the language
2014
+ * changes when the player starts again (tell the user, for example "Takes
2015
+ * effect after restart"). Every plugin with `events` hears the change in
2016
+ * {@link Appearance.language}.
2017
+ *
2018
+ * @capability ui.theme
2019
+ * @player 0.4.3
2020
+ *
2021
+ * @param language - `"system"` or a {@link Locale}.
2022
+ * @returns Resolves once the player saved it; {@link Appearance.language}
2023
+ * has the new value.
2024
+ *
2025
+ * @throws `invalid` (rejects) `language` is not `"system"` or a {@link Locale}.
2026
+ * @throws `capability` (rejects) The manifest does not declare `ui.theme`.
2027
+ * @throws `unsupported` (rejects) A player before 0.4.3.
2028
+ *
2029
+ * @example
2030
+ * ```ts
2031
+ * await plugin.appearance.setLanguage("de");
2032
+ * await plugin.osd.show("Takes effect after restart");
2033
+ * ```
2034
+ *
2035
+ * @since 1.5.9
2036
+ */
2037
+ setLanguage(language: LanguagePreference): Promise<void>;
2038
+ }
2039
+ /**
2040
+ * An interface language of the player: English (`en`, the source language
2041
+ * of every plugin text) or a translation. The list is `LOCALES` in
2042
+ * `@vestra-player/sdk/testing`.
2043
+ *
2044
+ * @remarks
2045
+ * Simplified Chinese is `zh-Hans`. There is no Traditional Chinese: a
2046
+ * system set to Traditional Chinese (Taiwan, Hong Kong, Macao) gets English.
2047
+ *
2048
+ * @since 1.5.9
2049
+ * @category UI
2050
+ */
2051
+ export type Locale = "en" | "zh-Hans" | "ja" | "ko" | "de" | "fr" | "es";
2052
+ /**
2053
+ * The saved interface language ({@link Appearance.language}): follow the
2054
+ * system, or one {@link Locale}.
2055
+ *
2056
+ * @since 1.5.9
2057
+ * @category UI
2058
+ */
2059
+ export type LanguagePreference = "system" | Locale;
2060
+ /**
2061
+ * Arguments of a template sentence ({@link Osd.show},
2062
+ * {@link OsdPanel.set}, {@link ConfirmOptions.args}, {@link I18n.text}): an
2063
+ * array for `{}` and `{0}`, `{1}` placeholders, or an object for `{name}`
2064
+ * placeholders. At most 16. Strings and finite numbers are data, shown as
2065
+ * they are (whole numbers without a fraction); a {@link Text} or
2066
+ * {@link TextList} argument (SDK 1.5.11, player 0.4.3) is translated too,
2067
+ * so a sentence can be built from translated parts.
2068
+ *
2069
+ * @since 1.5.9
2070
+ * @category UI
2071
+ */
2072
+ export type TemplateArgs = readonly TextArg[] | Readonly<Record<string, TextArg>>;
2073
+ /**
2074
+ * A text the player translates with the plugin's catalog and fills in:
2075
+ * English source text with its arguments. Made by {@link I18n.text}; plain
2076
+ * data, not a translation (the plugin never sees the translated string).
2077
+ *
2078
+ * @remarks
2079
+ * Accepted wherever the player shows a plugin's text: command titles, path
2080
+ * segments and reasons, {@link Osd.show} and {@link OsdPanel.set} (as the
2081
+ * text or an argument), the texts of {@link Dialogs.confirm}, file dialog
2082
+ * titles and `saveFile`'s `fileName`, and property values where Slint
2083
+ * expects a `string` (at any depth: a `[string]` model, a struct field).
2084
+ * Nested in another text's arguments it is translated too. Requires player
2085
+ * 0.4.3. Limits: `TEXT_LIMITS` in `@vestra-player/sdk/testing`.
2086
+ *
2087
+ * The wire form `{ $vestraText, args }` is what the player reads; write it
2088
+ * with {@link I18n.text}, so that `vestra-plugin i18n extract` finds the
2089
+ * source text.
2090
+ *
2091
+ * @since 1.5.11
2092
+ * @category UI
2093
+ */
2094
+ export interface Text {
2095
+ /** The English source text: a template with `{}`, `{0}` or `{name}`. */
2096
+ readonly $vestraText: string;
2097
+ /** Values for its placeholders. */
2098
+ readonly args?: TemplateArgs;
2099
+ }
2100
+ /**
2101
+ * Values joined the way the interface language joins them ("a, b" in
2102
+ * English, "a、b" in Chinese). Made by {@link I18n.list}; accepted where a
2103
+ * {@link Text} is.
2104
+ *
2105
+ * @since 1.5.11
2106
+ * @category UI
2107
+ */
2108
+ export interface TextList {
2109
+ /** The items: data, or texts the player translates. */
2110
+ readonly $vestraList: readonly TextArg[];
2111
+ /** How they are joined; `"list"` by default. */
2112
+ readonly style?: ListStyle;
2113
+ }
2114
+ /**
2115
+ * How a {@link TextList} is joined, per interface language:
2116
+ *
2117
+ * | style | English | 简体中文 | 日本語 |
2118
+ * | --- | --- | --- | --- |
2119
+ * | `list` (enumeration) | `, ` | `、` | `、` |
2120
+ * | `clauses` (parts of one sentence) | `, ` | `,` | `、` |
2121
+ * | `semicolons` | `; ` | `;` | `;` |
2122
+ * | `sentences` (no final full stop) | `. ` | `。` | `。` |
2123
+ * | `lines` | line break | line break | line break |
2124
+ *
2125
+ * Korean, German and Spanish join as English; French puts a narrow space
2126
+ * before `;`.
2127
+ *
2128
+ * @since 1.5.11
2129
+ * @category UI
2130
+ */
2131
+ export type ListStyle = "list" | "clauses" | "semicolons" | "sentences" | "lines";
2132
+ /**
2133
+ * An argument of a template: data (a string or a finite number, shown as it
2134
+ * is) or a {@link Text} or {@link TextList} (translated).
2135
+ *
2136
+ * @since 1.5.11
2137
+ * @category UI
2138
+ */
2139
+ export type TextArg = string | number | Text | TextList;
2140
+ /**
2141
+ * A text the player shows: English source text (translated as a whole), a
2142
+ * {@link Text} or a {@link TextList}.
2143
+ *
2144
+ * @since 1.5.11
2145
+ * @category UI
2146
+ */
2147
+ export type TextValue = string | Text | TextList;
2148
+ /**
2149
+ * The interface language of this run. `plugin.i18n`.
2150
+ *
2151
+ * @remarks
2152
+ * Plugins write no translation code. Every user-visible text is English
2153
+ * source text in one of three places, and the player translates it with
2154
+ * the plugin's own catalog (`locales/<lang>.po`, compiled into the package
2155
+ * by `vestra-plugin build`):
2156
+ *
2157
+ * 1. declarations: command titles and paths, settings page and panel
2158
+ * titles, the manifest's `name`, `punchline`, `description` and `tags`;
2159
+ * 2. Slint: `@tr("…")` in the plugin's `.slint` files;
2160
+ * 3. sentences the plugin hands the player: {@link Osd.show},
2161
+ * {@link OsdPanel.set} and {@link Dialogs.confirm} take a template and
2162
+ * its arguments (`osd.show("Speed {}×", [rate])`), never a sentence
2163
+ * composed in TypeScript.
2164
+ *
2165
+ * Where words and data cannot be separated that way (a menu item with a
2166
+ * number, a `[string]` model, a sentence made of translated parts), the
2167
+ * plugin passes a text value ({@link I18n.text}, {@link I18n.list}; SDK
2168
+ * 1.5.11): data the player translates, never a translated string.
2169
+ *
2170
+ * The language is fixed when the player starts: {@link I18n.locale} never
2171
+ * changes during a run. Read it for what is not text, such as the order of
2172
+ * names or a date format. `vestra-plugin i18n extract` collects the texts
2173
+ * into `locales/messages.pot`; see the
2174
+ * [i18n guide](../../docs/how-to/i18n.md).
2175
+ *
2176
+ * @capability None.
2177
+ *
2178
+ * @example Format a date the way the interface language writes it
2179
+ * ```ts
2180
+ * const day = new Date().toLocaleDateString(plugin.i18n.locale);
2181
+ * await plugin.osd.show("Today is {}", [day]);
2182
+ * ```
2183
+ *
2184
+ * @since 1.5.9
2185
+ * @category UI
2186
+ */
2187
+ export interface I18n {
2188
+ /**
2189
+ * The interface language of this run, chosen at start-up from the saved
2190
+ * {@link Appearance.language} or the system's languages; English when
2191
+ * neither names a {@link Locale}. Players before 0.4.3 report `"en"`.
2192
+ */
2193
+ readonly locale: Locale;
2194
+ /**
2195
+ * A text that mixes words with data, for the player to translate: the
2196
+ * English source text (a template) and its arguments. It returns a plain
2197
+ * {@link Text} value, not a translation; pass it where the player shows
2198
+ * text (see {@link Text}) and the player shows it translated with this
2199
+ * plugin's catalog. `vestra-plugin i18n extract` collects `source` from
2200
+ * every call, so write it as a literal (or a conditional of literals, or
2201
+ * an `as const` table entry).
2202
+ *
2203
+ * Arguments are data unless they are texts themselves, so a sentence can
2204
+ * be built from translated parts without composing it in TypeScript.
2205
+ * `text` does not use `this`: `const { text, list } = plugin.i18n` works.
2206
+ *
2207
+ * @param source - English source text; `{}`, `{0}` or `{name}` for the
2208
+ * arguments, `{{` and `}}` for braces.
2209
+ * @param args - Values for its placeholders.
2210
+ * @throws `invalid` A malformed or over-limit value (`TEXT_LIMITS`).
2211
+ *
2212
+ * @example A ComboBox model with fixed words and data
2213
+ * ```ts
2214
+ * const { text } = plugin.i18n;
2215
+ * const picker = plugin.windows.create("ui/clip.slint");
2216
+ * const tracks = [{ id: 7, title: "en" }];
2217
+ * picker.set({
2218
+ * labels: [text("No Audio"), ...tracks.map((t) => text("Track {} · {}", [t.id, t.title]))],
2219
+ * });
2220
+ * ```
2221
+ *
2222
+ * @example A message built from translated parts
2223
+ * ```ts
2224
+ * await plugin.osd.show("Download failed: {}", [plugin.i18n.text("Connection timed out")]);
2225
+ * ```
2226
+ *
2227
+ * @capability None.
2228
+ *
2229
+ * @since 1.5.11
2230
+ */
2231
+ text(source: string, args?: TemplateArgs): Text;
2232
+ /**
2233
+ * Items joined the way the interface language joins them (see
2234
+ * {@link ListStyle}): `list(["a.glsl", "b.glsl"])` shows "a.glsl, b.glsl"
2235
+ * in English and "a.glsl、b.glsl" in Chinese. Items are data, or texts the
2236
+ * player translates. Accepted where a {@link Text} is.
2237
+ *
2238
+ * @param items - At most 256.
2239
+ * @param style - `"list"` by default.
2240
+ * @throws `invalid` A malformed or over-limit value.
2241
+ *
2242
+ * @example
2243
+ * ```ts
2244
+ * const missing = ["a.glsl", "b.glsl"];
2245
+ * await plugin.osd.show("Removed missing shaders: {}", [plugin.i18n.list(missing)]);
2246
+ * ```
2247
+ *
2248
+ * @capability None.
2249
+ *
2250
+ * @since 1.5.11
2251
+ */
2252
+ list(items: readonly TextArg[], style?: ListStyle): TextList;
2253
+ /**
2254
+ * An `Error` whose words the player shows translated: throw it where the
2255
+ * player shows a plugin's failure to the user, such as from a
2256
+ * file or web resolver's `resolve` ({@link FileResolverOptions}, {@link WebResolverOptions}) (also when a stream
2257
+ * fails while it starts). The player shows `text` translated with this
2258
+ * plugin's catalog (as `player.state.error` and the failure message);
2259
+ * `message` is the English rendering, for logs and older players.
2260
+ * Elsewhere it is an ordinary `Error`.
2261
+ *
2262
+ * @param source - English source text, as {@link I18n.text}'s.
2263
+ * @param args - Values for its placeholders.
2264
+ * @throws `invalid` A malformed or over-limit value.
2265
+ *
2266
+ * @example
2267
+ * ```ts
2268
+ * plugin.resolvers.add({
2269
+ * id: "nes",
2270
+ * extensions: ["nes"],
2271
+ * async resolve() {
2272
+ * throw plugin.i18n.error("The ROM file was not found.");
2273
+ * },
2274
+ * });
2275
+ * ```
2276
+ *
2277
+ * @capability None.
2278
+ *
2279
+ * @since 1.5.11
2280
+ */
2281
+ error(source: string, args?: TemplateArgs): TextError;
2282
+ /**
2283
+ * The string a text shows in this run's language, for text that leaves
2284
+ * the player: what the plugin copies to the clipboard or writes to a file
2285
+ * ("Copy info", "Save as…"). Translated with this plugin's catalog (a
2286
+ * string as a whole, a {@link Text} or {@link TextList} with its
2287
+ * arguments), the way the player would show it.
2288
+ *
2289
+ * Only for text leaving the player. Never use it for the interface (a
2290
+ * window, the menu, the OSD, a dialog): pass the value itself there, so
2291
+ * the player translates it and `vestra-plugin i18n check` can see the
2292
+ * sentence is whole.
2293
+ *
2294
+ * @param value - Source text, or a text value.
2295
+ * @returns The translated string.
2296
+ * @throws `invalid` A malformed value.
2297
+ *
2298
+ * @example
2299
+ * ```ts
2300
+ * const { text, list, resolve } = plugin.i18n;
2301
+ * const lines = list([text("Version {}", ["1.0"]), text("Cores: {}", [8])], "lines");
2302
+ * await plugin.clipboard.writeText(resolve(lines));
2303
+ * ```
2304
+ *
2305
+ * @capability None.
2306
+ *
2307
+ * @since 1.5.11
2308
+ */
2309
+ resolve(value: TextValue): string;
2310
+ }
2311
+ /**
2312
+ * An `Error` the player shows translated ({@link I18n.error}).
2313
+ *
2314
+ * @since 1.5.11
2315
+ * @category UI
2316
+ */
2317
+ export interface TextError extends Error {
2318
+ /** What the player shows. */
2319
+ readonly text: Text;
1943
2320
  }
1944
2321
  /**
1945
2322
  * The categories of the Options window. The tree shows them in this order:
@@ -2352,6 +2729,9 @@ export interface TimelineLayer extends Disposable {
2352
2729
  * Replace the layer (last write wins; `[]` clears it). At most 1000 marks,
2353
2730
  * titles ≤ 100 characters, times within the media. Failures go to `onError`;
2354
2731
  * with `ifCurrent`, a write for another source is dropped silently.
2732
+ * A title is data (a chapter's or bookmark's own name), or a {@link Text}
2733
+ * the player translates (`text("Chapter {}", [n])`, SDK 1.5.11); other
2734
+ * plugins read the shown string.
2355
2735
  *
2356
2736
  * @example
2357
2737
  * ```ts
@@ -2366,7 +2746,7 @@ export interface TimelineLayer extends Disposable {
2366
2746
  set(marks: {
2367
2747
  time: number;
2368
2748
  kind: MarkKind;
2369
- title?: string;
2749
+ title?: string | Text;
2370
2750
  }[], options?: {
2371
2751
  ifCurrent?: PlayerState;
2372
2752
  }): void;
@@ -2560,17 +2940,17 @@ export interface DisplayInfo {
2560
2940
  */
2561
2941
  export interface ConfirmOptions {
2562
2942
  /** The question, shown in bold; 1–200 characters. */
2563
- message: string;
2943
+ message: TextValue;
2564
2944
  /** More text under the message; at most 2000 characters. */
2565
- detail?: string;
2945
+ detail?: TextValue;
2566
2946
  /** The confirm button's text (the default button: Enter picks it); 1–40 characters. */
2567
- confirm: string;
2947
+ confirm: TextValue;
2568
2948
  /**
2569
2949
  * The cancel button's text (Escape picks it); 1–40 characters.
2570
2950
  *
2571
2951
  * @defaultValue "Cancel", in the player's language
2572
2952
  */
2573
- cancel?: string;
2953
+ cancel?: TextValue;
2574
2954
  /**
2575
2955
  * Mark the confirm button as destructive: red text on macOS, a warning
2576
2956
  * icon on Windows.
@@ -2579,7 +2959,19 @@ export interface ConfirmOptions {
2579
2959
  */
2580
2960
  destructive?: boolean;
2581
2961
  /** A checkbox under the text, such as "Don't ask again"; 1–40 characters. */
2582
- checkbox?: string;
2962
+ checkbox?: TextValue;
2963
+ /**
2964
+ * Values for the placeholders of `message` and `detail` (player 0.4.3).
2965
+ * Any of the texts may instead be a {@link Text} (SDK 1.5.11), which
2966
+ * carries its own arguments; `args` only fills the string ones.
2967
+ * Every text of the dialog is English source text that the player shows
2968
+ * translated from the plugin's catalog; `message` and `detail` are
2969
+ * templates like {@link Osd.show}'s. The length limits apply to the shown
2970
+ * text.
2971
+ *
2972
+ * @since 1.5.9
2973
+ */
2974
+ args?: TemplateArgs;
2583
2975
  }
2584
2976
  /**
2585
2977
  * The system's own dialogs: a confirmation alert and the open and save file
@@ -2690,8 +3082,8 @@ export interface Dialogs {
2690
3082
  * @since 1.0.0
2691
3083
  */
2692
3084
  openFile(options?: {
2693
- /** The dialog's title, at most 256 bytes. */
2694
- title?: string;
3085
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3086
+ title?: TextValue;
2695
3087
  }): Promise<string | null>;
2696
3088
  /**
2697
3089
  * Ask the user for one or more files to open.
@@ -2715,8 +3107,8 @@ export interface Dialogs {
2715
3107
  * @since 1.0.0
2716
3108
  */
2717
3109
  openFiles(options?: {
2718
- /** The dialog's title, at most 256 bytes. */
2719
- title?: string;
3110
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3111
+ title?: TextValue;
2720
3112
  }): Promise<string[]>;
2721
3113
  /**
2722
3114
  * Ask the user for a folder.
@@ -2740,8 +3132,8 @@ export interface Dialogs {
2740
3132
  * @since 1.0.0
2741
3133
  */
2742
3134
  openDirectory(options?: {
2743
- /** The dialog's title, at most 256 bytes. */
2744
- title?: string;
3135
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3136
+ title?: TextValue;
2745
3137
  }): Promise<string | null>;
2746
3138
  /**
2747
3139
  * Ask the user where to save a file.
@@ -2771,10 +3163,15 @@ export interface Dialogs {
2771
3163
  * @since 1.0.0
2772
3164
  */
2773
3165
  saveFile(options?: {
2774
- /** The dialog's title, at most 256 bytes. */
2775
- title?: string;
2776
- /** The proposed file name: a plain name, at most 256 bytes. */
2777
- fileName?: string;
3166
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3167
+ title?: TextValue;
3168
+ /**
3169
+ * The proposed file name: a plain name, at most 256 bytes. A string is
3170
+ * data (not translated); a {@link Text} (SDK 1.5.11) is shown
3171
+ * translated, `text("Playlist.m3u8")` (a `/` or `\\` in a translation
3172
+ * becomes `-`).
3173
+ */
3174
+ fileName?: TextValue;
2778
3175
  }): Promise<string | null>;
2779
3176
  }
2780
3177
  /** One audio track of the current media ({@link PlayerState.audioTracks}). */
@@ -3349,18 +3746,38 @@ export interface OsdPanel extends Disposable {
3349
3746
  * @capability ui.osd
3350
3747
  *
3351
3748
  * @param text - The text, with `\n` between lines: at most 32 lines and
3352
- * 4096 bytes. `null` hides the block.
3749
+ * 4096 bytes once translated and formatted. `null` hides the block. A
3750
+ * template, like {@link Osd.show}'s.
3751
+ * @param args - Values for its placeholders (player 0.4.3); `null` or
3752
+ * omitted for none.
3753
+ * @param options - `maxColumns`: cut long lines (SDK 1.5.11).
3353
3754
  *
3354
3755
  * @example
3355
3756
  * ```ts
3356
3757
  * const info = plugin.osd.panel();
3357
- * info.set("A: 12.0 s\nB: 18.5 s");
3758
+ * info.set("A: {} s\nB: {} s", ["12.0", "18.5"]);
3358
3759
  * info.set(null); // hide it
3359
3760
  * ```
3360
3761
  *
3361
3762
  * @since 1.0.0
3362
3763
  */
3363
- set(text: string | null): void;
3764
+ set(text: TextValue | null, args?: TemplateArgs | null, options?: OsdPanelOptions): void;
3765
+ }
3766
+ /**
3767
+ * Options of {@link OsdPanel.set}.
3768
+ *
3769
+ * @since 1.5.11
3770
+ * @category UI
3771
+ */
3772
+ export interface OsdPanelOptions {
3773
+ /**
3774
+ * Cut every line longer than this many columns, after translation and
3775
+ * formatting, ending it with `…`: a wide character (Chinese, Japanese,
3776
+ * Korean, full-width forms) is two columns, a combining mark none, any
3777
+ * other one. An integer from 8 to 1000 (player 0.4.3; older players show
3778
+ * whole lines). Measure the video area to choose it.
3779
+ */
3780
+ maxColumns?: number;
3364
3781
  }
3365
3782
  /**
3366
3783
  * Text the player draws over the video: short messages and an information
@@ -3398,24 +3815,38 @@ export interface Osd {
3398
3815
  * Replaces the message on screen, whichever plugin showed it. A message
3399
3816
  * that is on screen when the plugin stops still disappears on time.
3400
3817
  *
3818
+ * The text is a template in English: the player shows its translation
3819
+ * from the plugin's catalog, with the placeholders (`{}`, `{0}`, `{name}`;
3820
+ * `{{` and `}}` for braces) filled from `args`. Without `args` the text
3821
+ * shows as it is (braces included). Untranslated, the English template is
3822
+ * formatted. Never compose the sentence in TypeScript
3823
+ * (`` `Speed ${rate}×` ``): translators can only translate whole templates.
3824
+ * An argument that is itself words is a {@link Text} (SDK 1.5.11), and a
3825
+ * message made of a varying number of parts is a {@link TextList} of
3826
+ * texts; `text` may also be a `Text` (then without `args`).
3827
+ *
3401
3828
  * @capability ui.osd
3402
3829
  *
3403
- * @param text - 1–200 characters; line breaks become spaces.
3830
+ * @param text - The template, 1–200 characters once translated and
3831
+ * formatted; line breaks become spaces.
3832
+ * @param args - Values for its placeholders (player 0.4.3; older players
3833
+ * show the template as it is).
3404
3834
  * @returns Resolves once the message is on screen.
3405
3835
  *
3406
- * @throws `invalid` (rejects) `text` is empty, only white space, or longer
3407
- * than 200 characters.
3836
+ * @throws `invalid` (rejects) The shown text is empty, only white space,
3837
+ * or longer than 200 characters, or `args` is not a {@link TemplateArgs}.
3408
3838
  * @throws `unavailable` (rejects) The player runs without a user interface.
3409
3839
  * @throws `capability` (rejects) The manifest does not declare `ui.osd`.
3410
3840
  *
3411
3841
  * @example
3412
3842
  * ```ts
3413
3843
  * await plugin.osd.show("Bookmark added");
3844
+ * await plugin.osd.show("Speed {}×", [plugin.player.state.rate]);
3414
3845
  * ```
3415
3846
  *
3416
3847
  * @since 1.0.0
3417
3848
  */
3418
- show(text: string): Promise<void>;
3849
+ show(text: TextValue, args?: TemplateArgs): Promise<void>;
3419
3850
  /**
3420
3851
  * Create this plugin's information block.
3421
3852
  *
@@ -3720,7 +4151,9 @@ export interface Subtitles {
3720
4151
  /**
3721
4152
  * Upload a subtitle file's bytes (UTF-8 SRT, or ASS/SSA with
3722
4153
  * `format: "ass"`) for the current source; select it unless
3723
- * `select: "none"`. `title` (1–200 characters) names the track. Aborting
4154
+ * `select: "none"`. `title` (1–200 characters) names the track: data, or
4155
+ * a {@link Text} the player translates (`text("{} · {}", [text("Simplified
4156
+ * Chinese"), file])`, SDK 1.5.11). Aborting
3724
4157
  * `signal` before the commit discards the upload (rejects with AbortError);
3725
4158
  * once the commit was sent it completes.
3726
4159
  *
@@ -3730,7 +4163,7 @@ export interface Subtitles {
3730
4163
  */
3731
4164
  load(bytes: Uint8Array, options?: SubtitleConditional & {
3732
4165
  format?: "srt" | "ass";
3733
- title?: string;
4166
+ title?: string | Text;
3734
4167
  select?: "primary" | "secondary" | "none";
3735
4168
  signal?: AbortSignal;
3736
4169
  }): Promise<SubtitleTrack>;
@@ -4207,6 +4640,30 @@ export interface PluginInfo {
4207
4640
  * @since 1.5.2
4208
4641
  */
4209
4642
  warnings?: readonly PluginWarning[];
4643
+ /**
4644
+ * The manifest's display texts in the interface language, translated
4645
+ * with that package's catalog; absent from players before 0.4.3 (read
4646
+ * `manifest.json` there).
4647
+ *
4648
+ * @since 1.5.9
4649
+ */
4650
+ display?: PluginDisplay;
4651
+ }
4652
+ /**
4653
+ * {@link PluginInfo.display}: a package's `name`, `punchline`,
4654
+ * `description` and `tags` as the player shows them.
4655
+ *
4656
+ * @since 1.5.9
4657
+ */
4658
+ export interface PluginDisplay {
4659
+ /** `name`, translated; `null` when the manifest has none. */
4660
+ readonly name: string | null;
4661
+ /** `punchline`, translated; `null` when the manifest has none. */
4662
+ readonly punchline: string | null;
4663
+ /** `description`, translated; `null` when the manifest has none. */
4664
+ readonly description: string | null;
4665
+ /** `tags`, each translated. */
4666
+ readonly tags: readonly string[];
4210
4667
  }
4211
4668
  /**
4212
4669
  * One refusal in {@link PluginInfo.warnings}.
@@ -4293,8 +4750,20 @@ export interface BindingRef {
4293
4750
  export interface BindingInfo extends BindingRef {
4294
4751
  /** Menu title. */
4295
4752
  title: string;
4296
- /** Menu path; `null` for a command outside the menu. */
4753
+ /** Menu path as shown; `null` for a command outside the menu. */
4297
4754
  path: string[] | null;
4755
+ /**
4756
+ * The menu path's keys: the English text menus merge by, the same in
4757
+ * every interface language (`["Playback", "Seek", "Back 5 s"]`; a
4758
+ * built-in menu by its key, `"Video"`, also when declared as `@video`).
4759
+ * `null` for a command outside the menu, and from players before 0.4.3.
4760
+ * Find a command by where it is in the menu with this, not with `path`.
4761
+ * Always set by `list()`; optional so that test fakes written for older
4762
+ * SDKs still type-check.
4763
+ *
4764
+ * @since 1.5.11
4765
+ */
4766
+ key?: string[] | null;
4298
4767
  /** Global, or only while the plugin's panel has focus. */
4299
4768
  scope: "global" | "panel";
4300
4769
  /** The command's own bindings. */