Change:
*  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ <del> import { cloneAndChange } from 'vs/base/common/objects'; </del> <ins> import { isObject, isString } from 'vs/base/common/types'; import { ILocalizedString } from 'vs/platform/action/common/action'; </ins> import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; <del> const nlsRegex = /^%([wd.-]+)%$/i; </del> <ins> import { localize } from 'vs/nls'; </ins> export interface ITranslations { [key: string]: string | { message: string; comment: string[] }; } <del> export function localizeManifest(manifest: IExtensionManifest, translations: ITranslations, fallbackTranslations?: ITranslations): IExtensionManifest { const patcher = (value: string): string | undefined => { if (typeof value !== 'string') { return undefined; } const match = nlsRegex.exec(value); </del> <ins> export function localizeManifest(extensionManifest: IExtensionManifest, translations: ITranslations, fallbackTranslations?: ITranslations): IExtensionManifest { try { replaceNLStrings(extensionManifest, translations, fallbackTranslations); } catch (error) { /*Ignore Error*/ } return extensionManifest; } </ins> <del> if (!match) { return undefined; </del> <ins> /** * This routine makes the following assumptions: * The root element is an object literal */ function replaceNLStrings(extensionManifest: IExtensionManifest, messages: ITranslations, originalMessages?: ITranslations): void { const processEntry = (obj: any, key: string | number, command?: boolean) => { const value = obj[key]; if (isString(value)) { const str = <string>value; const length = str.length; if (length > 1 && str[0] === '%' && str[length - 1] === '%') { const messageKey = str.substr(1, length - 2); let translated = messages[messageKey]; // If the messages come from a language pack they might miss some keys // Fill them from the original messages. if (translated === undefined && originalMessages) { translated = originalMessages[messageKey]; } const message: string | undefined = typeof translated === 'string' ? translated : translated.message; if (message !== undefined) { // This branch returns ILocalizedString's instead of Strings so that the Command Palette can contain both the localized and the original value. const original = originalMessages?.[messageKey]; const originalMessage: string | undefined = typeof original === 'string' ? original : original?.message; if ( // if we are translating the title or category of a command command && (key === 'title' || key === 'category') && // and the original value is not the same as the translated value originalMessage && originalMessage !== message ) { const localizedString: ILocalizedString = { value: message, original: originalMessage }; obj[key] = localizedString; } else { obj[key] = message; } } else { console.warn(`[${extensionManifest.name}]: ${localize('missingNLSKey', "Couldn't find message for key {0}.", messageKey)}`); } } } else if (isObject(value)) { for (const k in value) { if (value.hasOwnProperty(k)) { k === 'commands' ? processEntry(value, k, true) : processEntry(value, k, command); } } } else if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { processEntry(value, i, command); } </ins> } <del> const translation = translations?.[match[1]] ?? fallbackTranslations?.[match[1]] ?? value; return typeof translation === 'string' ? translation : (typeof translation.message === 'string' ? translation.message : value); </del> }; <del> return cloneAndChange(manifest, patcher); </del> <ins> for (const key in extensionManifest) { if (extensionManifest.hasOwnProperty(key)) { processEntry(extensionManifest, key); } } </ins> }

Discussion:
Testing in Firefox with de-ch (Swiss German) set. Open command palette and search for Remote Repositories commands :bug: The first command has a partly English, partly de-ch line under it ( and in English, seems to be de-ch translated) !
Hmm yeah it should look like this (Desktop screenshot) with the English strings next to the German strings. I think this is a bug in how I'm applying the translations... !
Just found out about these functions on the desktop side: Which seems to go through the manifest and turn some s into s. I followed up with to understand how this works.
This code was originally written by so I would ask him to know how this works.
Not quite, it was originally written by , but I'm happy to try and help. My understanding is that the code is taking a JSON object that looks like this: Together with another JSON object that looks like this: And produces a JSON object that looks like this by substituting all string values of the shape with their corresponding value:
I still see this on firefox: ! The first option's english translation still looks wrong? (tested on )
The first one is still wrong because of so we'll track that there. However, the rest now have the English string underneath which they use to not have, which means this is fixed and verified