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;
 | |
| }
 |