0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

feat(cli): support sync array keys (#4903)

* feat(cli): support sync array keys

* refactor(cli): update translate sync concurrency to 5

* chore: add changeset
This commit is contained in:
Gao Sun 2023-11-20 23:23:53 +08:00 committed by GitHub
parent 7379cfe4bd
commit 4b90782ae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 34 deletions

View file

@ -0,0 +1,7 @@
---
"@logto/cli": minor
---
support sync phrases array keys and update concurrency to 5
as the new model works with more concurrency.

View file

@ -7,12 +7,42 @@ import ts from 'typescript';
import { consoleLog } from '../../../utils.js'; import { consoleLog } from '../../../utils.js';
const getValue = (property: ts.PropertyAssignment): string => {
if (
ts.isStringLiteral(property.initializer) ||
ts.isNoSubstitutionTemplateLiteral(property.initializer)
) {
return property.initializer.getText();
}
consoleLog.fatal('Unsupported property:', property.getFullText());
};
const hasLeadingUntranslatedComment = (fullText: string) => {
const commentRanges = ts.getLeadingCommentRanges(fullText, 0);
if (commentRanges?.[0]) {
if (commentRanges.length > 1) {
consoleLog.fatal('Multiple comments found for property.');
}
const commentRange = commentRanges[0];
const comment = fullText.slice(commentRange.pos, commentRange.end);
if (comment.includes(untranslatedMark)) {
return true;
}
consoleLog.warn('Unsupported comment:', comment);
}
return false;
};
type FileStructure = { type FileStructure = {
[key: string]: { filePath?: string; structure: FileStructure }; [key: string | number]: { filePath?: string; structure: FileStructure };
}; };
type NestedPhraseObject = { type NestedPhraseObject = {
[key: string]: [phrase: string, isTranslated: boolean] | NestedPhraseObject; [key: string | number]: [phrase: string, isTranslated: boolean] | NestedPhraseObject;
}; };
const untranslatedMark = 'UNTRANSLATED'; const untranslatedMark = 'UNTRANSLATED';
@ -118,35 +148,32 @@ export const parseLocaleFiles = (filePath: string): ParsedTuple => {
nestedObject[key] = phrases; nestedObject[key] = phrases;
fileStructure[key] = { structure }; fileStructure[key] = { structure };
/* eslint-enable @silverhand/fp/no-mutation */ /* eslint-enable @silverhand/fp/no-mutation */
} else if ( } else if (ts.isArrayLiteralExpression(property.initializer)) {
ts.isStringLiteral(property.initializer) || const elements = property.initializer.elements.map((element) => {
ts.isNoSubstitutionTemplateLiteral(property.initializer) if (ts.isStringLiteralLike(element)) {
) { return [
const value = property.initializer.getText(); [element.getText(), !hasLeadingUntranslatedComment(element.getFullText())],
{},
const commentRanges = ts.getLeadingCommentRanges(property.getFullText(), 0); ];
if (commentRanges?.[0]) {
if (commentRanges.length > 1) {
consoleLog.fatal('Multiple comments found for property:', property);
} }
const commentRange = commentRanges[0]; return traverseNode(element, {}, {});
const comment = property.getFullText().slice(commentRange.pos, commentRange.end); });
if (comment.includes(untranslatedMark)) {
// eslint-disable-next-line @silverhand/fp/no-mutation /* eslint-disable @silverhand/fp/no-mutation */
nestedObject[key] = [value, false]; nestedObject[key] = Object.fromEntries(
} else { elements.map(([phrases], index) => [index, phrases])
// eslint-disable-next-line @silverhand/fp/no-mutation );
nestedObject[key] = [value, true]; fileStructure[key] = {
consoleLog.warn('Unsupported comment:', comment); structure: Object.fromEntries(
} elements.map(([, structure], index) => [index, { structure }])
} else { ),
// eslint-disable-next-line @silverhand/fp/no-mutation };
nestedObject[key] = [value, true]; /* eslint-enable @silverhand/fp/no-mutation */
}
} else { } else {
consoleLog.fatal('Unsupported property:', property); const value = getValue(property);
// eslint-disable-next-line @silverhand/fp/no-mutation
nestedObject[key] = [value, !hasLeadingUntranslatedComment(property.getFullText())];
} }
} }
}; };
@ -260,15 +287,18 @@ const traverseNode = async (
// Recursively traverse the nested object of phrases and the file structure // Recursively traverse the nested object of phrases and the file structure
// of the baseline language // of the baseline language
// eslint-disable-next-line complexity
const traverseObject = async ( const traverseObject = async (
baseline: ParsedTuple, baseline: ParsedTuple,
targetObject: NestedPhraseObject, targetObject: NestedPhraseObject,
tabSize: number tabSize: number
) => { ) => {
const [baselineObject, baselineStructure] = baseline; const [baselineObject, baselineStructure] = baseline;
const isBaselineArray = 0 in baselineObject;
for (const [key, value] of Object.entries(baselineObject)) { for (const [key, value] of Object.entries(baselineObject)) {
const existingValue = targetObject[key]; const existingValue = targetObject[key];
const keyOutput = isBaselineArray ? '' : `${key}: `;
if (Array.isArray(value)) { if (Array.isArray(value)) {
const [phrase] = value; const [phrase] = value;
@ -280,11 +310,11 @@ const traverseNode = async (
if (Array.isArray(existingValue) && existingValue[1]) { if (Array.isArray(existingValue) && existingValue[1]) {
await fs.appendFile( await fs.appendFile(
targetFilePath, targetFilePath,
`${' '.repeat(tabSize)}${key}: ${existingValue[0]},\n` `${' '.repeat(tabSize)}${keyOutput}${existingValue[0]},\n`
); );
} else { } else {
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}/** ${untranslatedMark} */\n`); await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}/** ${untranslatedMark} */\n`);
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${key}: ${phrase},\n`); await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${keyOutput}${phrase},\n`);
} }
} }
// Not a string, treat it as a nested object or an import // Not a string, treat it as a nested object or an import
@ -301,15 +331,22 @@ const traverseNode = async (
path.join(targetDirectory, keyStructure.filePath) path.join(targetDirectory, keyStructure.filePath)
); );
} }
// Otherwise, treat it as a nested object. // Otherwise, treat it as a nested object or array.
else { else {
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}${key}: {\n`); const isValueArray = 0 in value;
await fs.appendFile(
targetFilePath,
`${' '.repeat(tabSize)}${keyOutput}${isValueArray ? '[' : '{'}\n`
);
await traverseObject( await traverseObject(
[value, keyStructure?.structure ?? {}], [value, keyStructure?.structure ?? {}],
Array.isArray(existingValue) ? {} : existingValue ?? {}, Array.isArray(existingValue) ? {} : existingValue ?? {},
tabSize + 2 tabSize + 2
); );
await fs.appendFile(targetFilePath, `${' '.repeat(tabSize)}},\n`); await fs.appendFile(
targetFilePath,
`${' '.repeat(tabSize)}${isValueArray ? ']' : '}'},\n`
);
} }
} }
} }

View file

@ -13,7 +13,7 @@ const sync: CommandModule<{ path?: string }, { path?: string }> = {
describe: describe:
'Translate all untranslated phrases using ChatGPT. Note the environment variable `OPENAI_API_KEY` is required to work.', 'Translate all untranslated phrases using ChatGPT. Note the environment variable `OPENAI_API_KEY` is required to work.',
handler: async ({ path: inputPath }) => { handler: async ({ path: inputPath }) => {
const queue = new PQueue({ concurrency: 1 }); const queue = new PQueue({ concurrency: 5 });
const instancePath = await inquireInstancePath(inputPath); const instancePath = await inquireInstancePath(inputPath);
for (const languageTag of Object.keys(languages)) { for (const languageTag of Object.keys(languages)) {