103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import MagicString from 'magic-string';
|
|
import { assertNever } from '../utils.js';
|
|
import type { Locale, ILocale } from '../../../locales/index.js';
|
|
import type { TextModification } from '../locale-inliner.js';
|
|
import type { Logger } from '../logger.js';
|
|
|
|
export function applyWithLocale(
|
|
sourceCode: MagicString,
|
|
modifications: TextModification[],
|
|
localeName: string,
|
|
localeJson: Locale,
|
|
fileLogger: Logger,
|
|
) {
|
|
for (const modification of modifications) {
|
|
switch (modification.type) {
|
|
case 'delete':
|
|
sourceCode.remove(modification.begin, modification.end);
|
|
break;
|
|
case 'insert':
|
|
sourceCode.appendRight(modification.begin, modification.text);
|
|
break;
|
|
case 'replace':
|
|
sourceCode.update(modification.begin, modification.end, modification.text);
|
|
break;
|
|
case 'localized': {
|
|
const accessed = getPropertyByPath(localeJson, modification.localizationKey);
|
|
if (accessed == null) {
|
|
fileLogger.warn(`Cannot find localization key ${modification.localizationKey.join('.')}`);
|
|
}
|
|
sourceCode.update(modification.begin, modification.end, JSON.stringify(accessed));
|
|
break;
|
|
}
|
|
case 'parameterized-function': {
|
|
const accessed = getPropertyByPath(localeJson, modification.localizationKey);
|
|
let replacement: string;
|
|
if (typeof accessed === 'string') {
|
|
replacement = formatFunction(accessed);
|
|
} else if (typeof accessed === 'object' && accessed !== null) {
|
|
replacement = `({${Object.entries(accessed).map(([key, value]) => `${JSON.stringify(key)}:${formatFunction(value)}`).join(',')}})`;
|
|
} else {
|
|
fileLogger.warn(`Cannot find localization key ${modification.localizationKey.join('.')}`);
|
|
replacement = '(() => "")'; // placeholder for missing locale
|
|
}
|
|
sourceCode.update(modification.begin, modification.end, replacement);
|
|
break;
|
|
|
|
function formatFunction(format: string): string {
|
|
const params = new Set<string>();
|
|
const components: string[] = [];
|
|
let lastIndex = 0;
|
|
for (const match of format.matchAll(/\{(.+?)}/g)) {
|
|
const [fullMatch, paramName] = match;
|
|
if (lastIndex < match.index) {
|
|
components.push(JSON.stringify(format.slice(lastIndex, match.index)));
|
|
}
|
|
params.add(paramName);
|
|
components.push(paramName);
|
|
lastIndex = match.index + fullMatch.length;
|
|
}
|
|
components.push(JSON.stringify(format.slice(lastIndex)));
|
|
|
|
// we replace with `(({name,count})=>(name+count+"some"))`
|
|
const paramList = Array.from(params).join(',');
|
|
let body = components.filter(x => x !== '""').join('+');
|
|
if (body === '') body = '""'; // if the body is empty, we return empty string
|
|
return `(({${paramList}})=>(${body}))`;
|
|
}
|
|
}
|
|
case 'locale-name': {
|
|
sourceCode.update(modification.begin, modification.end, modification.literal ? JSON.stringify(localeName) : localeName);
|
|
break;
|
|
}
|
|
case 'locale-json': {
|
|
// locale-json is inlined to place where initialize module-level variable which is executed only once.
|
|
// In such case we can use JSON.parse to speed up the parsing script.
|
|
// https://v8.dev/blog/cost-of-javascript-2019#json
|
|
sourceCode.update(modification.begin, modification.end, `JSON.parse(${JSON.stringify(JSON.stringify(localeJson))})`);
|
|
break;
|
|
}
|
|
default: {
|
|
assertNever(modification);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getPropertyByPath(localeJson: ILocale, localizationKey: string[]): string | object | null {
|
|
if (localizationKey.length === 0) return localeJson;
|
|
let current: ILocale | string = localeJson;
|
|
for (const key of localizationKey) {
|
|
if (typeof current !== 'object' || !(key in current)) {
|
|
return null; // Key not found
|
|
}
|
|
current = current[key];
|
|
}
|
|
return current;
|
|
}
|