@vestra-player/sdk 1.5.8 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  /**
@@ -360,9 +362,9 @@ export interface CommandState {
360
362
  export type Bindable<T> = T | (() => T);
361
363
  /**
362
364
  * 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`.
365
+ * `@<id>` or by its English key (see {@link CommandOptions.path}): Open,
366
+ * Playback, Subtitles, Video, Audio, Screen, Settings. The list is
367
+ * `MENU_IDS` in `@vestra-player/sdk/testing`.
366
368
  */
367
369
  export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "screen" | "settings";
368
370
  /**
@@ -379,7 +381,7 @@ export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "sc
379
381
  * ({@link MenuId}) sit at fixed orders, whatever is inside them: Open 110,
380
382
  * Playback 200, Subtitles 300, Video 400, Audio 500, Screen 710, Settings
381
383
  * 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
384
+ * (`["@video", "Blur"]`) or by its key (`["Video", "Blur"]`), so a plugin's
383
385
  * item never moves the player's menu. Directly inside a built-in menu,
384
386
  * `order` 0 (the default) sorts after every other order: an item without
385
387
  * `order` goes at the end, after the player's own items (which all use 100
@@ -402,13 +404,20 @@ export interface CommandOptions {
402
404
  * one segment is a top-level item. Omit it for a command that is not in the
403
405
  * main menu (bindings and panel menus only); `title` is then required.
404
406
  *
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"`).
407
+ * Segments are English source text, and menus merge by them: commands
408
+ * of different plugins with the same leading segments share a submenu.
409
+ * The player shows each segment and the title in the interface language
410
+ * from the declaring plugin's catalog (`locales/<lang>.po`, see the
411
+ * [i18n guide](../../docs/how-to/i18n.md)); the player's own menus
412
+ * (Open, Playback, Subtitles, Video, Audio, Screen, Settings, and
413
+ * Playback ▸ Speed, Playback ▸ Seek) always show the player's words.
414
+ * A built-in menu can also be named by its id, `@` plus a {@link MenuId}:
415
+ * `@open`, `@playback`, `@subtitles`, `@video`, `@audio`, `@screen` or
416
+ * `@settings`. Only the first segment is read as an id; an unknown id, or
417
+ * an id with nothing after it, fails with `invalid`. Players up to 0.4.2
418
+ * titled their menus in Chinese (视频…): there `["Video", "Blur"]` is a
419
+ * menu of its own, so plugins for those players write `@video`; player
420
+ * 0.4.3 still accepts the old Chinese titles of its own menus.
412
421
  *
413
422
  * @example
414
423
  * ```ts
@@ -416,7 +425,10 @@ export interface CommandOptions {
416
425
  * ```
417
426
  */
418
427
  path?: string[];
419
- /** Menu label; defaults to the last path segment. */
428
+ /**
429
+ * Menu label (English source text, shown translated); defaults to the
430
+ * last path segment.
431
+ */
420
432
  title?: string;
421
433
  /**
422
434
  * Sibling order in the menu, ascending (integer, default 0). A submenu sits
@@ -1940,6 +1952,121 @@ export interface Appearance {
1940
1952
  * @since 1.0.0
1941
1953
  */
1942
1954
  set(theme: ThemeId): Promise<void>;
1955
+ /**
1956
+ * The saved interface language: `"system"` (follow the system's
1957
+ * languages, the default) or a {@link Locale}.
1958
+ *
1959
+ * @remarks
1960
+ * This is the choice for the next start. The language of this run is
1961
+ * {@link I18n.locale} and never changes while the player runs. Read before
1962
+ * activation; with `events` it follows other plugins' changes, without it
1963
+ * only this plugin's own. Players before 0.4.3 report `"system"`.
1964
+ *
1965
+ * @since 1.5.9
1966
+ */
1967
+ readonly language: LanguagePreference;
1968
+ /**
1969
+ * Save the interface language for the next start.
1970
+ *
1971
+ * @remarks
1972
+ * The player keeps the choice in its interface state; the language
1973
+ * changes when the player starts again (tell the user, for example "Takes
1974
+ * effect after restart"). Every plugin with `events` hears the change in
1975
+ * {@link Appearance.language}.
1976
+ *
1977
+ * @capability ui.theme
1978
+ * @player 0.4.3
1979
+ *
1980
+ * @param language - `"system"` or a {@link Locale}.
1981
+ * @returns Resolves once the player saved it; {@link Appearance.language}
1982
+ * has the new value.
1983
+ *
1984
+ * @throws `invalid` (rejects) `language` is not `"system"` or a {@link Locale}.
1985
+ * @throws `capability` (rejects) The manifest does not declare `ui.theme`.
1986
+ * @throws `unsupported` (rejects) A player before 0.4.3.
1987
+ *
1988
+ * @example
1989
+ * ```ts
1990
+ * await plugin.appearance.setLanguage("de");
1991
+ * await plugin.osd.show("Takes effect after restart");
1992
+ * ```
1993
+ *
1994
+ * @since 1.5.9
1995
+ */
1996
+ setLanguage(language: LanguagePreference): Promise<void>;
1997
+ }
1998
+ /**
1999
+ * An interface language of the player: English (`en`, the source language
2000
+ * of every plugin text) or a translation. The list is `LOCALES` in
2001
+ * `@vestra-player/sdk/testing`.
2002
+ *
2003
+ * @remarks
2004
+ * Simplified Chinese is `zh-Hans`. There is no Traditional Chinese: a
2005
+ * system set to Traditional Chinese (Taiwan, Hong Kong, Macao) gets English.
2006
+ *
2007
+ * @since 1.5.9
2008
+ * @category UI
2009
+ */
2010
+ export type Locale = "en" | "zh-Hans" | "ja" | "ko" | "de" | "fr" | "es";
2011
+ /**
2012
+ * The saved interface language ({@link Appearance.language}): follow the
2013
+ * system, or one {@link Locale}.
2014
+ *
2015
+ * @since 1.5.9
2016
+ * @category UI
2017
+ */
2018
+ export type LanguagePreference = "system" | Locale;
2019
+ /**
2020
+ * Arguments of a template sentence ({@link Osd.show},
2021
+ * {@link OsdPanel.set}, {@link ConfirmOptions.args}): an array for `{}` and
2022
+ * `{0}`, `{1}` placeholders, or an object for `{name}` placeholders. At most
2023
+ * 16 strings and finite numbers; whole numbers print without a fraction.
2024
+ *
2025
+ * @since 1.5.9
2026
+ * @category UI
2027
+ */
2028
+ export type TemplateArgs = readonly (string | number)[] | Readonly<Record<string, string | number>>;
2029
+ /**
2030
+ * The interface language of this run. `plugin.i18n`.
2031
+ *
2032
+ * @remarks
2033
+ * Plugins write no translation code. Every user-visible text is English
2034
+ * source text in one of three places, and the player translates it with
2035
+ * the plugin's own catalog (`locales/<lang>.po`, compiled into the package
2036
+ * by `vestra-plugin build`):
2037
+ *
2038
+ * 1. declarations: command titles and paths, settings page and panel
2039
+ * titles, the manifest's `name`, `punchline`, `description` and `tags`;
2040
+ * 2. Slint: `@tr("…")` in the plugin's `.slint` files;
2041
+ * 3. sentences the plugin hands the player: {@link Osd.show},
2042
+ * {@link OsdPanel.set} and {@link Dialogs.confirm} take a template and
2043
+ * its arguments (`osd.show("Speed {}×", [rate])`), never a sentence
2044
+ * composed in TypeScript.
2045
+ *
2046
+ * The language is fixed when the player starts: {@link I18n.locale} never
2047
+ * changes during a run. Read it for what is not text, such as the order of
2048
+ * names or a date format. `vestra-plugin i18n extract` collects the texts
2049
+ * into `locales/messages.pot`; see the
2050
+ * [i18n guide](../../docs/how-to/i18n.md).
2051
+ *
2052
+ * @capability None.
2053
+ *
2054
+ * @example Format a date the way the interface language writes it
2055
+ * ```ts
2056
+ * const day = new Date().toLocaleDateString(plugin.i18n.locale);
2057
+ * await plugin.osd.show("Today is {}", [day]);
2058
+ * ```
2059
+ *
2060
+ * @since 1.5.9
2061
+ * @category UI
2062
+ */
2063
+ export interface I18n {
2064
+ /**
2065
+ * The interface language of this run, chosen at start-up from the saved
2066
+ * {@link Appearance.language} or the system's languages; English when
2067
+ * neither names a {@link Locale}. Players before 0.4.3 report `"en"`.
2068
+ */
2069
+ readonly locale: Locale;
1943
2070
  }
1944
2071
  /**
1945
2072
  * The categories of the Options window. The tree shows them in this order:
@@ -2580,6 +2707,16 @@ export interface ConfirmOptions {
2580
2707
  destructive?: boolean;
2581
2708
  /** A checkbox under the text, such as "Don't ask again"; 1–40 characters. */
2582
2709
  checkbox?: string;
2710
+ /**
2711
+ * Values for the placeholders of `message` and `detail` (player 0.4.3).
2712
+ * Every text of the dialog is English source text that the player shows
2713
+ * translated from the plugin's catalog; `message` and `detail` are
2714
+ * templates like {@link Osd.show}'s. The length limits apply to the shown
2715
+ * text.
2716
+ *
2717
+ * @since 1.5.9
2718
+ */
2719
+ args?: TemplateArgs;
2583
2720
  }
2584
2721
  /**
2585
2722
  * The system's own dialogs: a confirmation alert and the open and save file
@@ -3349,18 +3486,20 @@ export interface OsdPanel extends Disposable {
3349
3486
  * @capability ui.osd
3350
3487
  *
3351
3488
  * @param text - The text, with `\n` between lines: at most 32 lines and
3352
- * 4096 bytes. `null` hides the block.
3489
+ * 4096 bytes once translated and formatted. `null` hides the block. A
3490
+ * template, like {@link Osd.show}'s.
3491
+ * @param args - Values for its placeholders (player 0.4.3).
3353
3492
  *
3354
3493
  * @example
3355
3494
  * ```ts
3356
3495
  * const info = plugin.osd.panel();
3357
- * info.set("A: 12.0 s\nB: 18.5 s");
3496
+ * info.set("A: {} s\nB: {} s", ["12.0", "18.5"]);
3358
3497
  * info.set(null); // hide it
3359
3498
  * ```
3360
3499
  *
3361
3500
  * @since 1.0.0
3362
3501
  */
3363
- set(text: string | null): void;
3502
+ set(text: string | null, args?: TemplateArgs): void;
3364
3503
  }
3365
3504
  /**
3366
3505
  * Text the player draws over the video: short messages and an information
@@ -3398,24 +3537,35 @@ export interface Osd {
3398
3537
  * Replaces the message on screen, whichever plugin showed it. A message
3399
3538
  * that is on screen when the plugin stops still disappears on time.
3400
3539
  *
3540
+ * The text is a template in English: the player shows its translation
3541
+ * from the plugin's catalog, with the placeholders (`{}`, `{0}`, `{name}`;
3542
+ * `{{` and `}}` for braces) filled from `args`. Without `args` the text
3543
+ * shows as it is (braces included). Untranslated, the English template is
3544
+ * formatted. Never compose the sentence in TypeScript
3545
+ * (`` `Speed ${rate}×` ``): translators can only translate whole templates.
3546
+ *
3401
3547
  * @capability ui.osd
3402
3548
  *
3403
- * @param text - 1–200 characters; line breaks become spaces.
3549
+ * @param text - The template, 1–200 characters once translated and
3550
+ * formatted; line breaks become spaces.
3551
+ * @param args - Values for its placeholders (player 0.4.3; older players
3552
+ * show the template as it is).
3404
3553
  * @returns Resolves once the message is on screen.
3405
3554
  *
3406
- * @throws `invalid` (rejects) `text` is empty, only white space, or longer
3407
- * than 200 characters.
3555
+ * @throws `invalid` (rejects) The shown text is empty, only white space,
3556
+ * or longer than 200 characters, or `args` is not a {@link TemplateArgs}.
3408
3557
  * @throws `unavailable` (rejects) The player runs without a user interface.
3409
3558
  * @throws `capability` (rejects) The manifest does not declare `ui.osd`.
3410
3559
  *
3411
3560
  * @example
3412
3561
  * ```ts
3413
3562
  * await plugin.osd.show("Bookmark added");
3563
+ * await plugin.osd.show("Speed {}×", [plugin.player.state.rate]);
3414
3564
  * ```
3415
3565
  *
3416
3566
  * @since 1.0.0
3417
3567
  */
3418
- show(text: string): Promise<void>;
3568
+ show(text: string, args?: TemplateArgs): Promise<void>;
3419
3569
  /**
3420
3570
  * Create this plugin's information block.
3421
3571
  *
@@ -4207,6 +4357,30 @@ export interface PluginInfo {
4207
4357
  * @since 1.5.2
4208
4358
  */
4209
4359
  warnings?: readonly PluginWarning[];
4360
+ /**
4361
+ * The manifest's display texts in the interface language, translated
4362
+ * with that package's catalog; absent from players before 0.4.3 (read
4363
+ * `manifest.json` there).
4364
+ *
4365
+ * @since 1.5.9
4366
+ */
4367
+ display?: PluginDisplay;
4368
+ }
4369
+ /**
4370
+ * {@link PluginInfo.display}: a package's `name`, `punchline`,
4371
+ * `description` and `tags` as the player shows them.
4372
+ *
4373
+ * @since 1.5.9
4374
+ */
4375
+ export interface PluginDisplay {
4376
+ /** `name`, translated; `null` when the manifest has none. */
4377
+ readonly name: string | null;
4378
+ /** `punchline`, translated; `null` when the manifest has none. */
4379
+ readonly punchline: string | null;
4380
+ /** `description`, translated; `null` when the manifest has none. */
4381
+ readonly description: string | null;
4382
+ /** `tags`, each translated. */
4383
+ readonly tags: readonly string[];
4210
4384
  }
4211
4385
  /**
4212
4386
  * One refusal in {@link PluginInfo.warnings}.
package/dist/plugin/ui.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /** Tool windows, docked panels and system dialogs. */
2
+ import { templateArgs } from "./osd.js";
2
3
  import { readFileSync } from "node:fs";
3
4
  import { basename, extname, resolve } from "node:path";
4
5
  import { failure, Latest, quiet } from "./runtime.js";
@@ -651,7 +652,8 @@ export const dialogs = (scope) => {
651
652
  };
652
653
  return {
653
654
  async confirm(options) {
654
- const { message, detail, confirm, cancel, destructive, checkbox } = options ?? {};
655
+ const { message, detail, confirm, cancel, destructive, checkbox, args } = options ?? {};
656
+ const values = templateArgs(args);
655
657
  const length = (text, min, max) => typeof text === "string" &&
656
658
  [...text].length >= min &&
657
659
  [...text].length <= max;
@@ -669,6 +671,7 @@ export const dialogs = (scope) => {
669
671
  ...(cancel === undefined ? {} : { cancel }),
670
672
  ...(destructive === undefined ? {} : { destructive }),
671
673
  ...(checkbox === undefined ? {} : { checkbox }),
674
+ ...(values === undefined ? {} : { args: values }),
672
675
  });
673
676
  return {
674
677
  confirmed: answer.confirmed === true,
@@ -21,6 +21,8 @@ export declare class WindowService {
21
21
  appearance: string | undefined;
22
22
  /** The file resolvers' extensions, as the reply carried them (`plugin.resolvers.fileTypes`). */
23
23
  fileTypes: string[] | undefined;
24
+ /** The saved interface language, as the reply carried it (`plugin.appearance.language`). */
25
+ language: string | undefined;
24
26
  accept(facts: WindowFacts): void;
25
27
  read(): Promise<void>;
26
28
  watch(listener: (state: WindowState) => void): {
@@ -54,9 +54,13 @@ export class WindowService {
54
54
  appearance;
55
55
  /** The file resolvers' extensions, as the reply carried them (`plugin.resolvers.fileTypes`). */
56
56
  fileTypes;
57
+ /** The saved interface language, as the reply carried it (`plugin.appearance.language`). */
58
+ language;
57
59
  accept(facts) {
58
60
  if (facts.appearance)
59
61
  this.appearance = facts.appearance;
62
+ if (facts.language)
63
+ this.language = facts.language;
60
64
  if (Array.isArray(facts.fileTypes))
61
65
  this.fileTypes = [...facts.fileTypes];
62
66
  const next = view(facts);
package/dist/wire.d.ts CHANGED
@@ -112,6 +112,13 @@ export interface PackageInfo {
112
112
  slot?: string;
113
113
  owner?: string;
114
114
  }[];
115
+ /** The manifest's display texts in the interface language (player 0.4.3). */
116
+ display?: {
117
+ name: string | null;
118
+ punchline: string | null;
119
+ description: string | null;
120
+ tags: string[];
121
+ };
115
122
  }
116
123
  /** Where an installed package came from (`packages.install` `source`). */
117
124
  export type WireSource = {
@@ -171,6 +178,10 @@ export interface WindowFacts {
171
178
  appearance?: string;
172
179
  /** The file resolvers' extensions (`plugin.resolvers.fileTypes`; player 0.4.2). */
173
180
  fileTypes?: string[];
181
+ /** The saved interface language, `"system"` or a locale (player 0.4.3). */
182
+ language?: string;
183
+ /** The interface language of this run (player 0.4.3). */
184
+ locale?: string;
174
185
  }
175
186
  /** One timeline mark as the player merges them (`timeline.marks`). */
176
187
  export interface WireMark {
@@ -276,11 +287,20 @@ export interface WireSystemInfo {
276
287
  primary: boolean;
277
288
  }[];
278
289
  }
290
+ /** Template arguments on the wire (`osd.show`, `osd.panel`, `dialogs.confirm`). */
291
+ export type TemplateArgsJson = (string | number)[] | {
292
+ [name: string]: string | number;
293
+ };
279
294
  export interface Transport {
280
295
  request<T>(method: string, params?: Json): Promise<T>;
281
296
  on(event: string, listener: (data: unknown) => void): () => void;
282
297
  /** Replaces the real local services (test host). */
283
298
  local?: LocalServices;
299
+ /**
300
+ * The interface language of this run (`plugin.i18n.locale`): the runner
301
+ * passes the player's `VESTRA_LOCALE`, the test host its `locale`.
302
+ */
303
+ locale?: string;
284
304
  }
285
305
  export declare function createWire(transport: Transport): {
286
306
  processing: {
@@ -306,6 +326,7 @@ export declare function createWire(transport: Transport): {
306
326
  cancelled: boolean;
307
327
  paths: string[];
308
328
  }>;
329
+ /** Message and detail are templates formatted with `args` (player 0.4.3). */
309
330
  confirm: (options: {
310
331
  [key: string]: Json;
311
332
  }) => Promise<{
@@ -351,6 +372,8 @@ export declare function createWire(transport: Transport): {
351
372
  /** The palette (`ui.theme` to change it). */
352
373
  appearance: {
353
374
  set: (theme: string) => Promise<void>;
375
+ /** The interface language for the next start (player 0.4.3). */
376
+ setLanguage: (language: string) => Promise<void>;
354
377
  };
355
378
  /** Settings pages in the player's options window (`ui.settings`). */
356
379
  settings: {
@@ -469,9 +492,10 @@ export declare function createWire(transport: Transport): {
469
492
  } | null) => Promise<void>;
470
493
  step: (direction: "next" | "previous") => Promise<void>;
471
494
  };
495
+ /** `text` is a template in the plugin's catalog, formatted with `args` (player 0.4.3). */
472
496
  osd: {
473
- show: (text: string) => Promise<void>;
474
- panel: (text: string | null) => Promise<void>;
497
+ show: (text: string, args?: TemplateArgsJson) => Promise<void>;
498
+ panel: (text: string | null, args?: TemplateArgsJson) => Promise<void>;
475
499
  };
476
500
  layout: {
477
501
  state: () => Promise<{
package/dist/wire.js CHANGED
@@ -69,6 +69,7 @@ export function createWire(transport) {
69
69
  commands: commandClient(transport),
70
70
  dialogs: {
71
71
  open: (options) => transport.request("dialogs.open", options),
72
+ /** Message and detail are templates formatted with `args` (player 0.4.3). */
72
73
  confirm: (options) => transport.request("dialogs.confirm", options),
73
74
  },
74
75
  /** The main window: its state, size, level, pointer hiding and picture-in-picture. */
@@ -99,6 +100,8 @@ export function createWire(transport) {
99
100
  /** The palette (`ui.theme` to change it). */
100
101
  appearance: {
101
102
  set: (theme) => transport.request("appearance.set", { theme }),
103
+ /** The interface language for the next start (player 0.4.3). */
104
+ setLanguage: (language) => transport.request("appearance.setLanguage", { language }),
102
105
  },
103
106
  /** Settings pages in the player's options window (`ui.settings`). */
104
107
  settings: {
@@ -190,9 +193,16 @@ export function createWire(transport) {
190
193
  set: (flags) => transport.request("sequence.set", { flags }),
191
194
  step: (direction) => transport.request("sequence.step", { direction }),
192
195
  },
196
+ /** `text` is a template in the plugin's catalog, formatted with `args` (player 0.4.3). */
193
197
  osd: {
194
- show: (text) => transport.request("osd.show", { text }),
195
- panel: (text) => transport.request("osd.panel", { text }),
198
+ show: (text, args) => transport.request("osd.show", {
199
+ text,
200
+ ...(args === undefined ? {} : { args }),
201
+ }),
202
+ panel: (text, args) => transport.request("osd.panel", {
203
+ text,
204
+ ...(args === undefined ? {} : { args }),
205
+ }),
196
206
  },
197
207
  layout: {
198
208
  state: () => transport.request("layout.state"),