// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'I18nMixin' is a Mixin offering loading of internationalization
* strings. Typically it is used as [[i18n('someString')]] computed bindings or
* for this.i18n('foo'). It is not needed for HTML $i18n{otherString}, which is
* handled by a C++ templatizer.
*/
import {loadTimeData} from '//resources/js/load_time_data.js';
import type {SanitizeInnerHtmlOpts} from '//resources/js/parse_html_subset.js';
import {parseHtmlSubset, sanitizeInnerHtml} from '//resources/js/parse_html_subset.js';
import type {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {dedupingMixin} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
type Constructor<T> = new (...args: any[]) => T;
export const I18nMixin = dedupingMixin(
<T extends Constructor<PolymerElement>>(superClass: T): T&
Constructor<I18nMixinInterface> => {
class I18nMixin extends superClass implements I18nMixinInterface {
/**
* Returns a translated string where $1 to $9 are replaced by the given
* values.
* @param id The ID of the string to translate.
* @param varArgs Values to replace the placeholders $1 to $9 in the
* string.
* @return A translated, substituted string.
*/
private i18nRaw_(id: string, ...varArgs: Array<string|number>) {
return varArgs.length === 0 ? loadTimeData.getString(id) :
loadTimeData.getStringF(id, ...varArgs);
}
/**
* Returns a translated string where $1 to $9 are replaced by the given
* values. Also sanitizes the output to filter out dangerous HTML/JS.
* Use with Polymer bindings that are *not* inner-h-t-m-l.
* NOTE: This is not related to $i18n{foo} in HTML, see file overview.
* @param id The ID of the string to translate.
* @param varArgs Values to replace the placeholders $1 to $9 in the
* string.
* @return A translated, sanitized, substituted string.
*/
i18n(id: string, ...varArgs: Array<string|number>) {
const rawString = this.i18nRaw_(id, ...varArgs);
return parseHtmlSubset(`<b>${rawString}</b>`).firstChild!.textContent!
;
}
/**
* Similar to 'i18n', returns a translated, sanitized, substituted
* string. It receives the string ID and a dictionary containing the
* substitutions as well as optional additional allowed tags and
* attributes. Use with Polymer bindings that are inner-h-t-m-l, for
* example.
* @param id The ID of the string to translate.
*/
i18nAdvanced(id: string, opts?: SanitizeInnerHtmlOpts) {
opts = opts || {};
const rawString = this.i18nRaw_(id, ...(opts.substitutions || []));
return sanitizeInnerHtml(rawString, opts);
}
/**
* Similar to 'i18n', with an unused |locale| parameter used to trigger
* updates when the locale changes.
* @param locale The UI language used.
* @param id The ID of the string to translate.
* @param varArgs Values to replace the placeholders $1 to $9 in the
* string.
* @return A translated, sanitized, substituted string.
*/
i18nDynamic(_locale: string, id: string, ...varArgs: string[]) {
return this.i18n(id, ...varArgs);
}
/**
* Similar to 'i18nDynamic', but varArgs valus are interpreted as keys
* in loadTimeData. This allows generation of strings that take other
* localized strings as parameters.
* @param locale The UI language used.
* @param id The ID of the string to translate.
* @param varArgs Values to replace the placeholders $1 to $9
* in the string. Values are interpreted as strings IDs if found in
* the list of localized strings.
* @return A translated, sanitized, substituted string.
*/
i18nRecursive(locale: string, id: string, ...varArgs: string[]) {
let args = varArgs;
if (args.length > 0) {
// Try to replace IDs with localized values.
args = args.map(str => {
return this.i18nExists(str) ? loadTimeData.getString(str) : str;
});
}
return this.i18nDynamic(locale, id, ...args);
}
/**
* Returns true if a translation exists for |id|.
*/
i18nExists(id: string) {
return loadTimeData.valueExists(id);
}
}
return I18nMixin;
});
export interface I18nMixinInterface {
i18n(id: string, ...varArgs: Array<string|number>): string;
i18nAdvanced(id: string, opts?: SanitizeInnerHtmlOpts): TrustedHTML;
i18nDynamic(locale: string, id: string, ...varArgs: string[]): string;
i18nRecursive(locale: string, id: string, ...varArgs: string[]): string;
i18nExists(id: string): boolean;
}