@vestra-player/sdk 1.5.9 → 1.5.12

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.
@@ -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). |
@@ -341,8 +341,11 @@ export interface CommandState {
341
341
  checked: boolean | null;
342
342
  /** Shown as busy; like `enabled: false`, the player does not call `run`. Default false. */
343
343
  busy: boolean;
344
- /** Shown when the command is disabled or busy. */
345
- 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;
346
349
  }
347
350
  /**
348
351
  * A value, or a function the SDK re-evaluates on every update pass (see
@@ -419,17 +422,28 @@ export interface CommandOptions {
419
422
  * menu of its own, so plugins for those players write `@video`; player
420
423
  * 0.4.3 still accepts the old Chinese titles of its own menus.
421
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)").
429
+ *
422
430
  * @example
423
431
  * ```ts
424
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
+ * });
425
439
  * ```
426
440
  */
427
- path?: string[];
441
+ path?: (string | Text)[];
428
442
  /**
429
- * Menu label (English source text, shown translated); defaults to the
430
- * last path segment.
443
+ * Menu label (English source text, shown translated, or a {@link Text});
444
+ * defaults to the last path segment.
431
445
  */
432
- title?: string;
446
+ title?: string | Text;
433
447
  /**
434
448
  * Sibling order in the menu, ascending (integer, default 0). A submenu sits
435
449
  * where its first command sits, except a built-in menu, which keeps its
@@ -475,8 +489,11 @@ export interface CommandOptions {
475
489
  checked?: Bindable<boolean | null>;
476
490
  /** Busy state (default false); the player does not call `run` while busy. */
477
491
  busy?: Bindable<boolean>;
478
- /** Why the command is disabled or busy (shown to the user). */
479
- 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>;
480
497
  /**
481
498
  * Run the command (menu item, binding, panel menu or
482
499
  * `host.command(id).run()` in tests). It may return a promise; errors
@@ -643,15 +660,31 @@ export interface MarkdownOptions {
643
660
  * is refused (Slint treats `_` and `-` as the same, so use camelCase or
644
661
  * kebab-case).
645
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
+ *
646
668
  * A {@link MarkdownRef} goes where the Slint type is `[MarkdownBlock]`: the
647
669
  * whole value, or a field at any depth (SDK 1.5.4; older players refuse a
648
670
  * nested one). An {@link ImageRef} only as the whole value: nested in an
649
671
  * array or object it is `invalid`.
650
672
  *
651
- * Limits per value: nesting depth 8, 4096 values in total (a document is
652
- * one), 1024 rows per array, 64 KiB per string and 1 MiB of text in total,
653
- * the text of its Markdown documents included (256 KB per document). A
654
- * 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.
655
688
  *
656
689
  * @since 1.0.0
657
690
  * @category UI
@@ -671,7 +704,7 @@ export type PropValue = PropData | ImageRef;
671
704
  * @since 1.5.4
672
705
  * @category UI
673
706
  */
674
- export type PropData = null | boolean | number | string | MarkdownRef | PropData[] | {
707
+ export type PropData = null | boolean | number | string | MarkdownRef | Text | TextList | PropData[] | {
675
708
  [key: string]: PropData;
676
709
  };
677
710
  /**
@@ -914,7 +947,13 @@ export interface View {
914
947
  * the next update pass replaces the set value with the bound one.
915
948
  *
916
949
  * Never throws. A write that fails is retried with back-off and reported
917
- * 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).
918
957
  *
919
958
  * @capability That of the view: `ui.window`, `ui.mount` or `ui.settings`.
920
959
  *
@@ -947,7 +986,9 @@ export interface View {
947
986
  * run. A view can have several bindings; when two return the same name,
948
987
  * the one bound later wins.
949
988
  *
950
- * 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`).
951
992
  *
952
993
  * @capability That of the view: `ui.window`, `ui.mount` or `ui.settings`.
953
994
  *
@@ -2018,14 +2059,92 @@ export type Locale = "en" | "zh-Hans" | "ja" | "ko" | "de" | "fr" | "es";
2018
2059
  export type LanguagePreference = "system" | Locale;
2019
2060
  /**
2020
2061
  * 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.
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.
2024
2068
  *
2025
2069
  * @since 1.5.9
2026
2070
  * @category UI
2027
2071
  */
2028
- export type TemplateArgs = readonly (string | number)[] | Readonly<Record<string, string | number>>;
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;
2029
2148
  /**
2030
2149
  * The interface language of this run. `plugin.i18n`.
2031
2150
  *
@@ -2043,6 +2162,11 @@ export type TemplateArgs = readonly (string | number)[] | Readonly<Record<string
2043
2162
  * its arguments (`osd.show("Speed {}×", [rate])`), never a sentence
2044
2163
  * composed in TypeScript.
2045
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
+ *
2046
2170
  * The language is fixed when the player starts: {@link I18n.locale} never
2047
2171
  * changes during a run. Read it for what is not text, such as the order of
2048
2172
  * names or a date format. `vestra-plugin i18n extract` collects the texts
@@ -2067,6 +2191,132 @@ export interface I18n {
2067
2191
  * neither names a {@link Locale}. Players before 0.4.3 report `"en"`.
2068
2192
  */
2069
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;
2070
2320
  }
2071
2321
  /**
2072
2322
  * The categories of the Options window. The tree shows them in this order:
@@ -2479,6 +2729,9 @@ export interface TimelineLayer extends Disposable {
2479
2729
  * Replace the layer (last write wins; `[]` clears it). At most 1000 marks,
2480
2730
  * titles ≤ 100 characters, times within the media. Failures go to `onError`;
2481
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.
2482
2735
  *
2483
2736
  * @example
2484
2737
  * ```ts
@@ -2493,7 +2746,7 @@ export interface TimelineLayer extends Disposable {
2493
2746
  set(marks: {
2494
2747
  time: number;
2495
2748
  kind: MarkKind;
2496
- title?: string;
2749
+ title?: string | Text;
2497
2750
  }[], options?: {
2498
2751
  ifCurrent?: PlayerState;
2499
2752
  }): void;
@@ -2687,17 +2940,17 @@ export interface DisplayInfo {
2687
2940
  */
2688
2941
  export interface ConfirmOptions {
2689
2942
  /** The question, shown in bold; 1–200 characters. */
2690
- message: string;
2943
+ message: TextValue;
2691
2944
  /** More text under the message; at most 2000 characters. */
2692
- detail?: string;
2945
+ detail?: TextValue;
2693
2946
  /** The confirm button's text (the default button: Enter picks it); 1–40 characters. */
2694
- confirm: string;
2947
+ confirm: TextValue;
2695
2948
  /**
2696
2949
  * The cancel button's text (Escape picks it); 1–40 characters.
2697
2950
  *
2698
2951
  * @defaultValue "Cancel", in the player's language
2699
2952
  */
2700
- cancel?: string;
2953
+ cancel?: TextValue;
2701
2954
  /**
2702
2955
  * Mark the confirm button as destructive: red text on macOS, a warning
2703
2956
  * icon on Windows.
@@ -2706,9 +2959,11 @@ export interface ConfirmOptions {
2706
2959
  */
2707
2960
  destructive?: boolean;
2708
2961
  /** A checkbox under the text, such as "Don't ask again"; 1–40 characters. */
2709
- checkbox?: string;
2962
+ checkbox?: TextValue;
2710
2963
  /**
2711
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.
2712
2967
  * Every text of the dialog is English source text that the player shows
2713
2968
  * translated from the plugin's catalog; `message` and `detail` are
2714
2969
  * templates like {@link Osd.show}'s. The length limits apply to the shown
@@ -2827,8 +3082,8 @@ export interface Dialogs {
2827
3082
  * @since 1.0.0
2828
3083
  */
2829
3084
  openFile(options?: {
2830
- /** The dialog's title, at most 256 bytes. */
2831
- title?: string;
3085
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3086
+ title?: TextValue;
2832
3087
  }): Promise<string | null>;
2833
3088
  /**
2834
3089
  * Ask the user for one or more files to open.
@@ -2852,8 +3107,8 @@ export interface Dialogs {
2852
3107
  * @since 1.0.0
2853
3108
  */
2854
3109
  openFiles(options?: {
2855
- /** The dialog's title, at most 256 bytes. */
2856
- title?: string;
3110
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3111
+ title?: TextValue;
2857
3112
  }): Promise<string[]>;
2858
3113
  /**
2859
3114
  * Ask the user for a folder.
@@ -2877,8 +3132,8 @@ export interface Dialogs {
2877
3132
  * @since 1.0.0
2878
3133
  */
2879
3134
  openDirectory(options?: {
2880
- /** The dialog's title, at most 256 bytes. */
2881
- title?: string;
3135
+ /** The dialog's title (English source text or a {@link Text}), at most 256 bytes. */
3136
+ title?: TextValue;
2882
3137
  }): Promise<string | null>;
2883
3138
  /**
2884
3139
  * Ask the user where to save a file.
@@ -2908,10 +3163,15 @@ export interface Dialogs {
2908
3163
  * @since 1.0.0
2909
3164
  */
2910
3165
  saveFile(options?: {
2911
- /** The dialog's title, at most 256 bytes. */
2912
- title?: string;
2913
- /** The proposed file name: a plain name, at most 256 bytes. */
2914
- 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;
2915
3175
  }): Promise<string | null>;
2916
3176
  }
2917
3177
  /** One audio track of the current media ({@link PlayerState.audioTracks}). */
@@ -3488,7 +3748,9 @@ export interface OsdPanel extends Disposable {
3488
3748
  * @param text - The text, with `\n` between lines: at most 32 lines and
3489
3749
  * 4096 bytes once translated and formatted. `null` hides the block. A
3490
3750
  * template, like {@link Osd.show}'s.
3491
- * @param args - Values for its placeholders (player 0.4.3).
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).
3492
3754
  *
3493
3755
  * @example
3494
3756
  * ```ts
@@ -3499,7 +3761,23 @@ export interface OsdPanel extends Disposable {
3499
3761
  *
3500
3762
  * @since 1.0.0
3501
3763
  */
3502
- set(text: string | null, args?: TemplateArgs): 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;
3503
3781
  }
3504
3782
  /**
3505
3783
  * Text the player draws over the video: short messages and an information
@@ -3543,6 +3821,9 @@ export interface Osd {
3543
3821
  * shows as it is (braces included). Untranslated, the English template is
3544
3822
  * formatted. Never compose the sentence in TypeScript
3545
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`).
3546
3827
  *
3547
3828
  * @capability ui.osd
3548
3829
  *
@@ -3565,7 +3846,7 @@ export interface Osd {
3565
3846
  *
3566
3847
  * @since 1.0.0
3567
3848
  */
3568
- show(text: string, args?: TemplateArgs): Promise<void>;
3849
+ show(text: TextValue, args?: TemplateArgs): Promise<void>;
3569
3850
  /**
3570
3851
  * Create this plugin's information block.
3571
3852
  *
@@ -3870,7 +4151,9 @@ export interface Subtitles {
3870
4151
  /**
3871
4152
  * Upload a subtitle file's bytes (UTF-8 SRT, or ASS/SSA with
3872
4153
  * `format: "ass"`) for the current source; select it unless
3873
- * `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
3874
4157
  * `signal` before the commit discards the upload (rejects with AbortError);
3875
4158
  * once the commit was sent it completes.
3876
4159
  *
@@ -3880,7 +4163,7 @@ export interface Subtitles {
3880
4163
  */
3881
4164
  load(bytes: Uint8Array, options?: SubtitleConditional & {
3882
4165
  format?: "srt" | "ass";
3883
- title?: string;
4166
+ title?: string | Text;
3884
4167
  select?: "primary" | "secondary" | "none";
3885
4168
  signal?: AbortSignal;
3886
4169
  }): Promise<SubtitleTrack>;
@@ -4467,8 +4750,20 @@ export interface BindingRef {
4467
4750
  export interface BindingInfo extends BindingRef {
4468
4751
  /** Menu title. */
4469
4752
  title: string;
4470
- /** Menu path; `null` for a command outside the menu. */
4753
+ /** Menu path as shown; `null` for a command outside the menu. */
4471
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;
4472
4767
  /** Global, or only while the plugin's panel has focus. */
4473
4768
  scope: "global" | "panel";
4474
4769
  /** The command's own bindings. */
package/dist/plugin/ui.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /** Tool windows, docked panels and system dialogs. */
2
- import { templateArgs } from "./osd.js";
2
+ import { shownText, templateArgs } from "./osd.js";
3
+ import { isTextValue } from "../i18n.js";
3
4
  import { readFileSync } from "node:fs";
4
5
  import { basename, extname, resolve } from "node:path";
5
6
  import { failure, Latest, quiet } from "./runtime.js";
@@ -647,16 +648,27 @@ export const panels = (scope) => ({
647
648
  });
648
649
  export const dialogs = (scope) => {
649
650
  const open = async (kind, options = {}) => {
650
- const result = await scope.runtime.host.dialogs.open({ kind, ...options });
651
+ const result = await scope.runtime.host.dialogs.open({
652
+ kind,
653
+ ...(options.title === undefined
654
+ ? {}
655
+ : { title: shownText(options.title, undefined, "title") }),
656
+ ...(options.fileName === undefined
657
+ ? {}
658
+ : { fileName: shownText(options.fileName, undefined, "fileName") }),
659
+ });
651
660
  return result.cancelled ? [] : result.paths;
652
661
  };
653
662
  return {
654
663
  async confirm(options) {
655
664
  const { message, detail, confirm, cancel, destructive, checkbox, args } = options ?? {};
656
665
  const values = templateArgs(args);
657
- const length = (text, min, max) => typeof text === "string" &&
658
- [...text].length >= min &&
659
- [...text].length <= max;
666
+ // A string's length is checked here; a text value's (shown) length by
667
+ // the player.
668
+ const length = (text, min, max) => isTextValue(text) ||
669
+ (typeof text === "string" &&
670
+ [...text].length >= min &&
671
+ [...text].length <= max);
660
672
  if (!length(message, 1, 200) ||
661
673
  !length(confirm, 1, 40) ||
662
674
  (detail !== undefined && !length(detail, 0, 2000)) ||
@@ -664,13 +676,17 @@ export const dialogs = (scope) => {
664
676
  (checkbox !== undefined && !length(checkbox, 1, 40)) ||
665
677
  (destructive !== undefined && typeof destructive !== "boolean"))
666
678
  throw failure("invalid", "confirm needs message 1–200, confirm/cancel/checkbox 1–40, detail ≤ 2000 characters");
679
+ // `args` fills the string templates; a text value carries its own.
680
+ const shown = (text, what) => shownText(text, undefined, what);
667
681
  const answer = await scope.runtime.host.dialogs.confirm({
668
- message,
669
- confirm,
670
- ...(detail === undefined ? {} : { detail }),
671
- ...(cancel === undefined ? {} : { cancel }),
682
+ message: shown(message, "message"),
683
+ confirm: shown(confirm, "confirm"),
684
+ ...(detail === undefined ? {} : { detail: shown(detail, "detail") }),
685
+ ...(cancel === undefined ? {} : { cancel: shown(cancel, "cancel") }),
672
686
  ...(destructive === undefined ? {} : { destructive }),
673
- ...(checkbox === undefined ? {} : { checkbox }),
687
+ ...(checkbox === undefined
688
+ ? {}
689
+ : { checkbox: shown(checkbox, "checkbox") }),
674
690
  ...(values === undefined ? {} : { args: values }),
675
691
  });
676
692
  return {