Change:
<!-- Do Not Delete This! featurerequesttemplate --<!-- Please read our Rules of Conduct: --<!-- Please search existing issues to avoid creating duplicates. --<!-- Describe the feature you'd like. -- has existed for quite a while now... and in its original design it was very filepath driven. We felt this pain when bundling extensions became the norm and introduced a webpack loader and bundler that baked filepaths into source code... That mitigation of rewriting JS code worked... but this all became more complicated when talking about the web because the web has no "nice" way of loading strings synchronously. And even if there was such a nice thing, we wouldn't want synchronous loading from the internet... so instead we are moving in a direction where Core is responsible for loading the bundle of strings for an extension, asynchronously, before an extension is loaded and then those strings are provided to the extension via an API. In addition to this, we will have an additional field in the which is at the root next to and . This is useful for: being explicit in an extension that supports localized strings allows VS Code to not have to "guess" where the localized strings are in the extension (this is especially important in web where we don't have a filesystem that we can crawl) Important definitions: : the parameter is the English string. This is used for a few things: It's the string used when VS Code is in the default (English) language It's the fallback string when a bundle doesn't contain a translation for a particular string It's the key in the bundle (which is essentially just a big dictionary) who's value will be the translated version of itself in whatever language is chosen : This follows the same format we have when you wanna add a comment in a The array will be an array of comments that will be included in the exported XLF files (the files sent to translators). The is the same as defined above. : This will contain the key-value pairs of the strings that VS Code loaded. This is useful if you want to pass a subprocess, a web worker, or a webview the contents of the bundle so that that environment can have all the translations that are currently being used : This will contain the to the bundle that was loaded. This is useful if you want to pass a subprocess, a web worker, or a webview the Uri of the bundle so that that environment can load the translations that are currently being used. Along side of this work, we will ship a very small helper package called or something like that that can be given the or value and load the bundle and handle string interpolation. This library will really only be used for subprocesses/workers/webviews that can't depend on the API directly. Current API proposal: This is a CLI & library for managing the usages of . It has two main usecases: Export: export the English strings out of the code and put them in a familiar JSON or XLF format Import: imports XLF files and turns them into the respective and files It currently has three commands: Export Generate XLF Import XLF
Is it possible to see which language is currently being used via the API? Does anything need to be changed in an extension based on Left to Right languages (e.g. English) verses Right to Left languages (e.g. Arabic)? The main case I tend to see is layout, for example if there is a custom rendered view.
VS Code doesn't support RTL: and I suspect that idea of LTR vs RTL would come in as an API separate to this proposal.
I see: Will the sync up with language translation the API returns? How does this work if the language is changed? Does the entire editor and the extension reload? Is there a way to account for formatted strings like ` (Including the interpolated piece matters for translation, simply concatenating doesn't translate well). With it should be simple enough for a developer to create their own function to map the provided language code to a direction if the feature is needed for the extension.
Yes. will return the string in whatever is set to so long as there was a translation available for that string in that language. And fallback to English if not. Yes. This is what happens today and will continue to happen. Not at this time. We are aligning to what  already does but in theory something like that could be in the future. Yes but I think there's enough value here: String interpolation - the Automatic loading of your strings before your extension activates (so that any top level calls still work) - this is especially important in the web where we must load strings via an async fetch call Tooling around exporting strings out of your source code into a familiar format (keeping the same XLF format that uses) but additional formats could be
Updates from UX sync: isn't better than ... maybe ? use a function on instead of this half function/half object thing either take in or
Ok I have been playing with this a lot today… and here’s what I’d like to run: The API for translating strings in your extension’s code The tooling used for extracting l10n strings from vscode extensions and working with XLF files The library used for loading the translations into subprocesses of your extension The file used for translating static contributions in your extension’s This contains the following definition: The new namespace keeps the that we will see all over code short and sweet. Tooling used for extracting l10n strings from vscode extensions and working with XLF files. Here's what the CLI looks like: There is also a programatic way to interact with the tooling via JS. Library used for loading the translations into subprocesses of your extension. It has an API of: the same function a function that looks like this that will receive: ~NOTE: for backwards compatibility, is also supported.~ ~This file is used for translating static contributions in your extension's .~ deferred The api being pairs nicely with because you either do this in your extension code: or in a subprocess you do and the API becomes the same:
Moving to Oct and marking finalization. This has 2 TPIs associated with it:
api-notes document when/why is align bundle and uri value document sub-logic for , eg syntax for the function support document for params, namespace
This isn't so simple. What does the change to the API proposal look like to support this? Some scenarios: I think that: looks nicer than but I do want to support the object syntax
I agree with that. Could we be more strict and spec the var-args to not be but or ? That would allow you to disambiguate them and IMO it won't hurt any user of the var-args variant
I adopted this in GHPRI, ran into an issue, and discussed with Posting here for API discussion: I ended up with the following compile error: I assumed this was just a breaking API change so updated my loc API usage to this: saw this and commented that it's not the usual pattern for templates with a variable number of arguments.
Either extension the overload to accept or don't allow undefined to surface in messages support

Discussion:
// namespace: l10n const l10n: typeof vscode.l10n = { <del> t(...params: [message: string, ...args: Array<string | number>] | [message: string, args: Record<string, any>] | [{ message: string; args?: Array<string | number> | Record<string, any>; comment: string[] }]): string { checkProposedApiEnabled(extension, 'localization'); </del> <ins> t(...params: [message: string, ...args: Array<string | number | boolean>] | [message: string, args: Record<string, any>] | [{ message: string; args?: Array<string | number | boolean> | Record<string, any>; comment: string | string[] }]): string { </ins> if (typeof params[0] === 'string') { const key = params.shift() as string; <del> // We have either rest args which are Array<string | number> or an array with a single Record<string, any>. This ensures we get a // Record<string | number> which will be formatted correctly. </del> <ins> // We have either rest args which are Array<string | number | boolean> or an array with a single Record<string, any>. // This ensures we get a Record<string | number, any> which will be formatted correctly. </ins> const argsFormatted = !params || typeof params[0] !== 'object' ? params : params[0]; return extHostLocalization.getMessage(extension.identifier.value, { message: key, args: argsFormatted as Record<string | number, any> | undefined }); }

return extHostLocalization.getMessage(extension.identifier.value, params[0]); }, get bundle() { <del> checkProposedApiEnabled(extension, 'localization'); </del> return extHostLocalization.getBundle(extension.identifier.value); }, get uri() { <del> checkProposedApiEnabled(extension, 'localization'); </del> return extHostLocalization.getBundleUri(extension.identifier.value); } };

export interface IStringDetails { message: string; args?: Record<string | number, any>; <del> comment?: string[]; </del> <ins> comment?: string | string[]; </ins> } export interface ITestControllerPatch {

let key = message; if (comment && comment.length > 0) { <del> key += `/${comment.join()}`; </del> <ins> key += `/${Array.isArray(comment) ? comment.join() : comment}`; </ins> } const str = this.bundleCache.get(extensionId)?.contents[key]; if (!str) {

inlineCompletionsNew: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', <del> localization: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.localization.d.ts', </del> notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookContentProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts',

} /** <ins> * Namespace for localization-related functionality in the extension API. To use this properly, * you must have `l10n` defined in your `package.json` and have bundle.l10n.<language>.json files. * For more information on how to generate bundle.l10n.<language>.json files, check out the * [vscode-l10n repo](https://github.com/microsoft/vscode-l10n). */ 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). * @param message - The message to localize. Supports index templating where strings like `{0}` and `{1}` are * replaced by the item at that index in the {@link args} array. * @param args - The arguments to be used in the localized string. The index of the argument is used to * match the template placeholder in the localized string. * @returns localized string with injected arguments. * @example `l10n.localize('hello', 'Hello {0}!', 'World');` */ export function t(message: string, ...args: Array<string | number | boolean>): string; /** * 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). * @param message The message to localize. Supports named templating where strings like `{foo}` and `{bar}` are * replaced by the value in the Record for that key (foo, bar, etc). * @param args The arguments to be used in the localized string. The name of the key in the record is used to * match the template placeholder in the localized string. * @returns localized string with injected arguments. * @example `l10n.t('Hello {name}', { name: 'Erich' });` */ export function t(message: string, args: Record<string, any>): string; /** * 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 args values for any templated values). * @param options The options to use when localizing the message. * @returns localized string with injected arguments. */ export function t(options: { /** * The message to localize. If {@link args} is an array, this message supports index templating where strings like * `{0}` and `{1}` are replaced by the item at that index in the {@link args} array. If `args` is a `Record<string, any>`, * this supports named templating where strings like `{foo}` and `{bar}` are replaced by the value in * the Record for that key (foo, bar, etc). */ message: string; /** * The arguments to be used in the localized string. As an array, the index of the argument is used to * match the template placeholder in the localized string. As a Record, the key is used to match the template * placeholder in the localized string. */ args?: Array<string | number | boolean> | Record<string, any>; /** * A comment to help translators understand the context of the message. */ comment: string | string[]; }): string; /** * The bundle of localized strings that have been loaded for the extension. * It's undefined if no bundle has been loaded. The bundle is typically not loaded if * there was no bundle found or when we are running with the default language. */ export const bundle: { [key: string]: string } | undefined; /** * The URI of the localization bundle that has been loaded for the extension. * It's undefined if no bundle has been loaded. The bundle is typically not loaded if * there was no bundle found or when we are running with the default language. */ export const uri: Uri | undefined; } /** </ins> * Namespace for testing functionality. Tests are published by registering * {@link TestController} instances, then adding {@link TestItem TestItems}. * Controllers may also describe how to run tests by creating one or more

<del> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { /** * The namespace for localization-related functionality in the extension API. To use this properly, * you must have `l10n` defined in your `package.json` and have bundle.l10n.<language>.json files. * For more information on how to generate bundle.l10n.<language>.json files, check out the * [vscode-l10n repo](https://github.com/microsoft/vscode-l10n). */ 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). * @param message The message to localize. Supports index templating where strings like {0} and {1} are * replaced by the item at that index in the {@link args} array. * @param args The arguments to be used in the localized string. The index of the argument is used to * match the template placeholder in the localized string. * @returns localized string with injected arguments. * @example l10n.localize('hello', 'Hello {0}!', 'World'); */ export function t(message: string, ...args: Array<string | number>): string; /** * 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). * @param message The message to localize. Supports named templating where strings like {foo} and {bar} are * replaced by the value in the Record for that key (foo, bar, etc). * @param args The arguments to be used in the localized string. The name of the key in the record is used to * match the template placeholder in the localized string. * @returns localized string with injected arguments. * @example l10n.t('Hello {name}', { name: 'Erich' }); */ export function t(message: string, args: Record<string, any>): string; /** * 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 args values for any templated values). * @param options The options to use when localizing the message. * @returns localized string with injected arguments. */ export function t(options: { /** * The message to localize. If {@link args} is an array, this message supports index templating where strings like * {0} and {1} are replaced by the item at that index in the {@link args} array. If args is a Record<string, any>, * this supports named templating where strings like {foo} and {bar} are replaced by the value in * the Record for that key (foo, bar, etc). */ message: string; /** * The arguments to be used in the localized string. As an array, the index of the argument is used to * match the template placeholder in the localized string. As a Record, the key is used to match the template * placeholder in the localized string. */ args?: Array<string | number> | Record<string, any>; /** * A comment to help translators understand the context of the message. */ comment: string[]; }): string; /** * The bundle of localized strings that have been loaded for the extension. * It's undefined if no bundle has been loaded. The bundle is typically not loaded if * there was no bundle found or when we are running with the default language. */ export const bundle: { [key: string]: string } | undefined; /** * The URI of the localization bundle that has been loaded for the extension. * It's undefined if no bundle has been loaded. The bundle is typically not loaded if * there was no bundle found or when we are running with the default language. */ export const uri: Uri | undefined; } } </del>