blob: 17f8acdf3401dd090e236e8b3a86ea1769630bb5 [file]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as i18n from '../../core/i18n/i18n.js';
import type * as I18n from '../../third_party/i18n/i18n.js';
import * as Lit from '../../third_party/lit/lit.js';
export interface Static {
value: unknown;
$$static$$: true;
}
type TemplateValues = Static|unknown;
type FlattenedTemplateValues = {
strings: TemplateStringsArray,
valueMap: boolean[],
};
export function flattenTemplate(strings: TemplateStringsArray, ...values: TemplateValues[]): FlattenedTemplateValues {
const valueMap: boolean[] = [];
const newStrings: string[] = [];
// Start with an empty buffer and start running over the values.
let buffer = '';
for (let v = 0; v < values.length; v++) {
const possibleStatic = values[v];
if (isStaticLiteral(possibleStatic)) {
// If this is a static literal, add the current string plus the
// static literal's value to the buffer.
buffer += strings[v] + possibleStatic.value;
// Filter this value in future invocations.
valueMap.push(false);
} else {
// If we reach a non-static value, push what we have on to
// the new strings array, and reset the buffer.
buffer += strings[v];
newStrings.push(buffer);
buffer = '';
// Include this value in future invocations.
valueMap.push(true);
}
}
// Since the strings length is always the values length + 1, we need
// to append whatever that final string is to whatever is left in the
// buffer, and flush both out to the newStrings.
newStrings.push(buffer + strings[values.length]);
(newStrings as unknown as {raw: readonly string[]}).raw = [...newStrings];
return {strings: newStrings as unknown as TemplateStringsArray, valueMap};
}
export function html(strings: TemplateStringsArray, ...values: TemplateValues[]): Lit.TemplateResult {
if (values.some(value => isStaticLiteral(value))) {
return htmlWithStatics(strings, ...values);
}
return Lit.html(strings, ...values);
}
export function literal(value: TemplateStringsArray): Static {
return {
value: value[0],
$$static$$: true,
};
}
function isStaticLiteral(item: TemplateValues|unknown): item is Static {
return typeof item === 'object' && (item !== null && '$$static$$' in item);
}
const flattenedTemplates = new WeakMap<TemplateStringsArray, FlattenedTemplateValues>();
function htmlWithStatics(strings: TemplateStringsArray, ...values: TemplateValues[]): Lit.TemplateResult {
// Check to see if we've already converted this before.
const existing = flattenedTemplates.get(strings);
if (existing) {
const filteredValues = values.filter((a, index) => {
if (!existing) {
return false;
}
return existing.valueMap[index];
});
// Pass through to Lit.
return Lit.html(existing.strings, ...filteredValues);
}
flattenedTemplates.set(strings, flattenTemplate(strings, ...values));
return htmlWithStatics(strings, ...values);
}
/**
* @param placeholders placeholders must not contain localized strings or other localized templates as that is
* incompatible with languages using a different sentence structure or ordering (e.g., RTL).
*/
export function i18nTemplate(
registeredStrings: I18n.LocalizedStringSet.RegisteredFileStrings, stringId: string,
placeholders: Record<string, Lit.TemplateResult|string>): Lit.TemplateResult {
const formatter = registeredStrings.getLocalizedStringSetFor(i18n.DevToolsLocale.DevToolsLocale.instance().locale)
.getMessageFormatterFor(stringId);
let result = html``;
for (const icuElement of formatter.getAst()) {
if (icuElement.type === /* argumentElement */ 1) {
const placeholderValue = placeholders[icuElement.value];
if (placeholderValue) {
result = html`${result}${placeholderValue}`;
}
} else if ('value' in icuElement) {
result = html`${result}${icuElement.value}`;
}
}
return result;
}