microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
162.61k stars 28.66k forks source link

Proposal: add tagged template literal support for `vscode.l10n` #181555

Open connor4312 opened 1 year ago

connor4312 commented 1 year ago

Motivation

Currently, string localization is done by calling a method

vscode.l10n.t('Hello {0}!' name);

This mirrors what we do in core, however, this is a little error-prone. There's no validation in that string to ensure that replacement are done correctly, that all replacement arguments are used, or that enough replacement arguments are provided to satisfy the string. This is what initially inspired me 🙈

Proposal

We propose adding an overload to vscode.l10n.t that allows it to be used with tagged template literals. With tagged literalls, developers use a template literal as they "normally would", but prepend it with the method call

vscode.l10n.t`Hello ${name}!`

The associated definition for t() would gain an overload:

    export namespace l10n {
        /**
         * Marks a string for localization. If a localized bundle is available for the language specified by
         * {@link env.language} and the bundle has a localized value for this message, then that localized
         * value will be returned (with injected {@link args} values for any templated values).
         *
         * This signature is for use with tagged template literals. The more verbose object-based
         * call should be used if comments are required.
         *
         * @example
         * ```
         * vscode.l10n.t`Hello ${name}!`
         * ```
         *
         * @param message - String message components.
         * @param args - Replacement components in the string.
         * @returns localized string with injected arguments.
         * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
         */
        export function t(parts: TemplateStringsArray, ...args: Array<string | number | boolean>): string;

Tagged template literals are an ES5 feature, and we already use ES6 features (such as Symbol.iterator), so this does not introduce any new lib/target constraints.

Previously we haven't been big fans of adding "convenience" APIs such as this to the extension host API, but I think this might warrant some deviation since we force that this specific method is used (and not wrapped) in the localization extraction pipeline.

Alternatives

An alternative way to gain the same level of safety would be to use TS' template literal types to enforce argument placement. However, this lacks the ergonomic benefits of template literals, and would introduce previously unseen levels of type acrobatics to vscode.d.ts. Our extension host api is not a venue for Cirque du TypeScript.

jrieken commented 1 year ago

A question about NLS comments came up, one idea is to support tuples like { value, comment } so that each substitution value can be commented but that's still no way to leave a generic comment. Do you have an idea how that could work or is the answer to use the existing API then?

connor4312 commented 1 year ago

The answer would be to use the existing API for them. There are a few ways we thought about allowing this, but we didn't like any of them. From least to most terrible:

l10n.t({ comment: 'is a greeting' })`Hello ${name}!`
l10n.t`Hello ${name}! ${l10n.comment('is a greeting')}`
l10n.t`Hello ${name}!`.comment('is a greeting') // use `new String(input)` and put a method on it!
TylerLeonhardt commented 1 year ago

I'm gonna move this to the backlog for now since we wanna try this in Core first. The core issue is ^^