@intlayer/cli 5.5.9 → 5.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -9
- package/dist/cjs/cli.cjs +78 -6
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/cli.test.cjs +435 -0
- package/dist/cjs/cli.test.cjs.map +1 -0
- package/dist/cjs/fill.cjs +8 -12
- package/dist/cjs/fill.cjs.map +1 -1
- package/dist/cjs/index.cjs +5 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/prompts/REVIEW_PROMPT.md +37 -0
- package/dist/cjs/prompts/TRANSLATE_PROMPT.md +38 -0
- package/dist/cjs/pull.cjs +10 -2
- package/dist/cjs/pull.cjs.map +1 -1
- package/dist/cjs/pushConfig.cjs +5 -1
- package/dist/cjs/pushConfig.cjs.map +1 -1
- package/dist/cjs/reviewDoc.cjs +203 -0
- package/dist/cjs/reviewDoc.cjs.map +1 -0
- package/dist/cjs/translateDoc.cjs +201 -0
- package/dist/cjs/translateDoc.cjs.map +1 -0
- package/dist/cjs/utils/calculateChunks.cjs +120 -0
- package/dist/cjs/utils/calculateChunks.cjs.map +1 -0
- package/dist/cjs/utils/calculateChunks.test.cjs +104 -0
- package/dist/cjs/utils/calculateChunks.test.cjs.map +1 -0
- package/dist/cjs/utils/calculrateChunkTest.md +9 -0
- package/dist/cjs/utils/checkAIAccess.cjs +40 -0
- package/dist/cjs/utils/checkAIAccess.cjs.map +1 -0
- package/dist/cjs/utils/checkFileModifiedRange.cjs +97 -0
- package/dist/cjs/utils/checkFileModifiedRange.cjs.map +1 -0
- package/dist/cjs/utils/checkFileModifiedRange.test.cjs +175 -0
- package/dist/cjs/utils/checkFileModifiedRange.test.cjs.map +1 -0
- package/dist/cjs/utils/checkLastUpdateTime.cjs +33 -0
- package/dist/cjs/utils/checkLastUpdateTime.cjs.map +1 -0
- package/dist/cjs/utils/chunkInference.cjs +58 -0
- package/dist/cjs/utils/chunkInference.cjs.map +1 -0
- package/dist/cjs/utils/fixChunkStartEndChars.cjs +47 -0
- package/dist/cjs/utils/fixChunkStartEndChars.cjs.map +1 -0
- package/dist/cjs/utils/fixChunkStartEndChars.test.cjs +81 -0
- package/dist/cjs/utils/fixChunkStartEndChars.test.cjs.map +1 -0
- package/dist/cjs/utils/formatTimeDiff.cjs +46 -0
- package/dist/cjs/utils/formatTimeDiff.cjs.map +1 -0
- package/dist/cjs/utils/formatTimeDiff.test.cjs +32 -0
- package/dist/cjs/utils/formatTimeDiff.test.cjs.map +1 -0
- package/dist/cjs/utils/getChunk.cjs +77 -0
- package/dist/cjs/utils/getChunk.cjs.map +1 -0
- package/dist/cjs/utils/getChunk.test.cjs +46 -0
- package/dist/cjs/utils/getChunk.test.cjs.map +1 -0
- package/dist/cjs/utils/getIsFileUpdatedRecently.cjs +36 -0
- package/dist/cjs/utils/getIsFileUpdatedRecently.cjs.map +1 -0
- package/dist/cjs/utils/getOutputFilePath.cjs +89 -0
- package/dist/cjs/utils/getOutputFilePath.cjs.map +1 -0
- package/dist/cjs/utils/getOutputFilePath.test.cjs +73 -0
- package/dist/cjs/utils/getOutputFilePath.test.cjs.map +1 -0
- package/dist/cjs/utils/getParentPackageJSON.cjs +47 -0
- package/dist/cjs/utils/getParentPackageJSON.cjs.map +1 -0
- package/dist/cjs/utils/listSpecialChars.cjs +78 -0
- package/dist/cjs/utils/listSpecialChars.cjs.map +1 -0
- package/dist/cjs/utils/listSpecialChars.test.cjs +58 -0
- package/dist/cjs/utils/listSpecialChars.test.cjs.map +1 -0
- package/dist/cjs/utils/reorderParagraphs.cjs +125 -0
- package/dist/cjs/utils/reorderParagraphs.cjs.map +1 -0
- package/dist/cjs/utils/reorderParagraphs.test.cjs +71 -0
- package/dist/cjs/utils/reorderParagraphs.test.cjs.map +1 -0
- package/dist/cjs/utils/splitTextByLine.cjs +35 -0
- package/dist/cjs/utils/splitTextByLine.cjs.map +1 -0
- package/dist/cjs/utils/splitTextByLine.test.cjs +14 -0
- package/dist/cjs/utils/splitTextByLine.test.cjs.map +1 -0
- package/dist/esm/cli.mjs +79 -7
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/cli.test.mjs +412 -0
- package/dist/esm/cli.test.mjs.map +1 -0
- package/dist/esm/fill.mjs +8 -12
- package/dist/esm/fill.mjs.map +1 -1
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/prompts/REVIEW_PROMPT.md +37 -0
- package/dist/esm/prompts/TRANSLATE_PROMPT.md +38 -0
- package/dist/esm/pull.mjs +10 -2
- package/dist/esm/pull.mjs.map +1 -1
- package/dist/esm/pushConfig.mjs +5 -1
- package/dist/esm/pushConfig.mjs.map +1 -1
- package/dist/esm/reviewDoc.mjs +172 -0
- package/dist/esm/reviewDoc.mjs.map +1 -0
- package/dist/esm/translateDoc.mjs +170 -0
- package/dist/esm/translateDoc.mjs.map +1 -0
- package/dist/esm/utils/calculateChunks.mjs +96 -0
- package/dist/esm/utils/calculateChunks.mjs.map +1 -0
- package/dist/esm/utils/calculateChunks.test.mjs +103 -0
- package/dist/esm/utils/calculateChunks.test.mjs.map +1 -0
- package/dist/esm/utils/calculrateChunkTest.md +9 -0
- package/dist/esm/utils/checkAIAccess.mjs +16 -0
- package/dist/esm/utils/checkAIAccess.mjs.map +1 -0
- package/dist/esm/utils/checkFileModifiedRange.mjs +73 -0
- package/dist/esm/utils/checkFileModifiedRange.mjs.map +1 -0
- package/dist/esm/utils/checkFileModifiedRange.test.mjs +181 -0
- package/dist/esm/utils/checkFileModifiedRange.test.mjs.map +1 -0
- package/dist/esm/utils/checkLastUpdateTime.mjs +9 -0
- package/dist/esm/utils/checkLastUpdateTime.mjs.map +1 -0
- package/dist/esm/utils/chunkInference.mjs +34 -0
- package/dist/esm/utils/chunkInference.mjs.map +1 -0
- package/dist/esm/utils/fixChunkStartEndChars.mjs +23 -0
- package/dist/esm/utils/fixChunkStartEndChars.mjs.map +1 -0
- package/dist/esm/utils/fixChunkStartEndChars.test.mjs +80 -0
- package/dist/esm/utils/fixChunkStartEndChars.test.mjs.map +1 -0
- package/dist/esm/utils/formatTimeDiff.mjs +22 -0
- package/dist/esm/utils/formatTimeDiff.mjs.map +1 -0
- package/dist/esm/utils/formatTimeDiff.test.mjs +31 -0
- package/dist/esm/utils/formatTimeDiff.test.mjs.map +1 -0
- package/dist/esm/utils/getChunk.mjs +53 -0
- package/dist/esm/utils/getChunk.mjs.map +1 -0
- package/dist/esm/utils/getChunk.test.mjs +45 -0
- package/dist/esm/utils/getChunk.test.mjs.map +1 -0
- package/dist/esm/utils/getIsFileUpdatedRecently.mjs +12 -0
- package/dist/esm/utils/getIsFileUpdatedRecently.mjs.map +1 -0
- package/dist/esm/utils/getOutputFilePath.mjs +65 -0
- package/dist/esm/utils/getOutputFilePath.mjs.map +1 -0
- package/dist/esm/utils/getOutputFilePath.test.mjs +72 -0
- package/dist/esm/utils/getOutputFilePath.test.mjs.map +1 -0
- package/dist/esm/utils/getParentPackageJSON.mjs +23 -0
- package/dist/esm/utils/getParentPackageJSON.mjs.map +1 -0
- package/dist/esm/utils/listSpecialChars.mjs +54 -0
- package/dist/esm/utils/listSpecialChars.mjs.map +1 -0
- package/dist/esm/utils/listSpecialChars.test.mjs +57 -0
- package/dist/esm/utils/listSpecialChars.test.mjs.map +1 -0
- package/dist/esm/utils/reorderParagraphs.mjs +101 -0
- package/dist/esm/utils/reorderParagraphs.mjs.map +1 -0
- package/dist/esm/utils/reorderParagraphs.test.mjs +70 -0
- package/dist/esm/utils/reorderParagraphs.test.mjs.map +1 -0
- package/dist/esm/utils/splitTextByLine.mjs +11 -0
- package/dist/esm/utils/splitTextByLine.mjs.map +1 -0
- package/dist/esm/utils/splitTextByLine.test.mjs +13 -0
- package/dist/esm/utils/splitTextByLine.test.mjs.map +1 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.test.d.ts +2 -0
- package/dist/types/cli.test.d.ts.map +1 -0
- package/dist/types/fill.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/pull.d.ts.map +1 -1
- package/dist/types/pushConfig.d.ts.map +1 -1
- package/dist/types/reviewDoc.d.ts +25 -0
- package/dist/types/reviewDoc.d.ts.map +1 -0
- package/dist/types/translateDoc.d.ts +25 -0
- package/dist/types/translateDoc.d.ts.map +1 -0
- package/dist/types/utils/calculateChunks.d.ts +9 -0
- package/dist/types/utils/calculateChunks.d.ts.map +1 -0
- package/dist/types/utils/calculateChunks.test.d.ts +2 -0
- package/dist/types/utils/calculateChunks.test.d.ts.map +1 -0
- package/dist/types/utils/checkAIAccess.d.ts +4 -0
- package/dist/types/utils/checkAIAccess.d.ts.map +1 -0
- package/dist/types/utils/checkFileModifiedRange.d.ts +11 -0
- package/dist/types/utils/checkFileModifiedRange.d.ts.map +1 -0
- package/dist/types/utils/checkFileModifiedRange.test.d.ts +2 -0
- package/dist/types/utils/checkFileModifiedRange.test.d.ts.map +1 -0
- package/dist/types/utils/checkLastUpdateTime.d.ts +9 -0
- package/dist/types/utils/checkLastUpdateTime.d.ts.map +1 -0
- package/dist/types/utils/chunkInference.d.ts +12 -0
- package/dist/types/utils/chunkInference.d.ts.map +1 -0
- package/dist/types/utils/fixChunkStartEndChars.d.ts +2 -0
- package/dist/types/utils/fixChunkStartEndChars.d.ts.map +1 -0
- package/dist/types/utils/fixChunkStartEndChars.test.d.ts +2 -0
- package/dist/types/utils/fixChunkStartEndChars.test.d.ts.map +1 -0
- package/dist/types/utils/formatTimeDiff.d.ts +2 -0
- package/dist/types/utils/formatTimeDiff.d.ts.map +1 -0
- package/dist/types/utils/formatTimeDiff.test.d.ts +2 -0
- package/dist/types/utils/formatTimeDiff.test.d.ts.map +1 -0
- package/dist/types/utils/getChunk.d.ts +9 -0
- package/dist/types/utils/getChunk.d.ts.map +1 -0
- package/dist/types/utils/getChunk.test.d.ts +2 -0
- package/dist/types/utils/getChunk.test.d.ts.map +1 -0
- package/dist/types/utils/getIsFileUpdatedRecently.d.ts +5 -0
- package/dist/types/utils/getIsFileUpdatedRecently.d.ts.map +1 -0
- package/dist/types/utils/getOutputFilePath.d.ts +26 -0
- package/dist/types/utils/getOutputFilePath.d.ts.map +1 -0
- package/dist/types/utils/getOutputFilePath.test.d.ts +2 -0
- package/dist/types/utils/getOutputFilePath.test.d.ts.map +1 -0
- package/dist/types/utils/getParentPackageJSON.d.ts +32 -0
- package/dist/types/utils/getParentPackageJSON.d.ts.map +1 -0
- package/dist/types/utils/listSpecialChars.d.ts +10 -0
- package/dist/types/utils/listSpecialChars.d.ts.map +1 -0
- package/dist/types/utils/listSpecialChars.test.d.ts +2 -0
- package/dist/types/utils/listSpecialChars.test.d.ts.map +1 -0
- package/dist/types/utils/reorderParagraphs.d.ts +8 -0
- package/dist/types/utils/reorderParagraphs.d.ts.map +1 -0
- package/dist/types/utils/reorderParagraphs.test.d.ts +2 -0
- package/dist/types/utils/reorderParagraphs.test.d.ts.map +1 -0
- package/dist/types/utils/splitTextByLine.d.ts +2 -0
- package/dist/types/utils/splitTextByLine.d.ts.map +1 -0
- package/dist/types/utils/splitTextByLine.test.d.ts +2 -0
- package/dist/types/utils/splitTextByLine.test.d.ts.map +1 -0
- package/package.json +14 -12
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
You are an expert in internationalisation, technical documentation and multilingual content auditing. Your task is to **audit an existing translation** against its {{baseLocaleName}} source and make it fully up-to-date.
|
|
2
|
+
|
|
3
|
+
# Base instructions
|
|
4
|
+
|
|
5
|
+
1. **Goal of the audit**
|
|
6
|
+
|
|
7
|
+
- Insert any **missing paragraphs, lists, tables, code blocks or images** that have been introduced in the base file since the last translation.
|
|
8
|
+
- **Correct spelling, grammar, punctuation or Markdown syntax errors** that may exist in the current translation.
|
|
9
|
+
- Keep all **code blocks** and **variable names** in the same language as the base file, but translate the **comments** inside the code blocks to {{localeName}}. Example: `internationalization: {` should stay `internationalization: {`. You should not replace the `z` by a `s`.
|
|
10
|
+
- Do **not** change sentences that are already correct. Do not reflow or re-word translated sentences unless it is strictly necessary for correctness.
|
|
11
|
+
- To ensure all characters are included in the reviewed content, you should use "///chunkStart///" and "///chunkEnd///" to delimit the chunk of the reviewed content. You should not use "---" or "```" to delimit the chunk of the reviewed content.
|
|
12
|
+
|
|
13
|
+
2. **Output requirements**
|
|
14
|
+
|
|
15
|
+
- Return **only the full, updated file content in {{localeName}}** ‑ no explanations or surrounding code fences.
|
|
16
|
+
- All existing correct content must remain **byte-for-byte identical** unless a fix is required (see above).
|
|
17
|
+
- New or updated content must be placed in the correct position so that the overall structure mirrors the English file.
|
|
18
|
+
- If overlapping chunks are found, you should include the exact same content than provided in the user message.
|
|
19
|
+
|
|
20
|
+
3. **Locales**
|
|
21
|
+
|
|
22
|
+
- Source locale: {{baseLocaleName}}
|
|
23
|
+
- Target locale: {{localeName}}
|
|
24
|
+
|
|
25
|
+
4. **Output Example:**
|
|
26
|
+
|
|
27
|
+
Entry (en - English (US)): "///chunkStart/// - Here the translated content///chunkEnd///"
|
|
28
|
+
Entry (fr - French): "///chunkStart/// - Ici le contenu trraduit ce mardi 25 juin 2025///chunkEnd///"
|
|
29
|
+
Expected Output (fr - French): "///chunkStart/// - Ici le contenu traduit///chunkEnd///"
|
|
30
|
+
|
|
31
|
+
# Custom instructions
|
|
32
|
+
|
|
33
|
+
{{customInstructions}}
|
|
34
|
+
|
|
35
|
+
# Application context
|
|
36
|
+
|
|
37
|
+
{{applicationContext}}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
You are an expert in internationalization and content management. Your task is to translate the following documentation into {{localeName}}.
|
|
2
|
+
|
|
3
|
+
# Base instructions
|
|
4
|
+
|
|
5
|
+
1. **Requirement:**
|
|
6
|
+
|
|
7
|
+
- You should only translate the text, and titles of the file.
|
|
8
|
+
- You should not alter the structure of the file.
|
|
9
|
+
- You should not alter the code logic of code elements.
|
|
10
|
+
- You should consider the context of the solution (Intlayer), and write for the user of the solution (developer).
|
|
11
|
+
- In the code elements, the naming of the variables should be made in the same language as the base file. But the comments should be in {{localeName}}.
|
|
12
|
+
- You should return the translated file in {{localeName}} without any additional comments or explanations.
|
|
13
|
+
- To ensure all characters are included in the translated content, you should use "///chunkStart///" and "///chunkEnd///" to delimit the translated content. You should not use "---" or "```" to delimit the translated content.
|
|
14
|
+
- Anyway the length of the chunk to translate, you should translate the whole chunk and do not skip any part. You should ensure that each part of the chunk is translated.
|
|
15
|
+
- Each title should be translated and present in the output file.
|
|
16
|
+
- Each listing point should be translated and present in the output file.
|
|
17
|
+
- Each code element should be translated and present in the output file.
|
|
18
|
+
- Each image should be translated and present in the output file.
|
|
19
|
+
- Each link should be translated and present in the output file.
|
|
20
|
+
- If overlapping chunks are found, you should include the exact same content than provided in the user message.
|
|
21
|
+
|
|
22
|
+
2. **Locales:**
|
|
23
|
+
|
|
24
|
+
- Base file locale: {{baseLocaleName}}
|
|
25
|
+
- Desired Final file language: {{localeName}}
|
|
26
|
+
|
|
27
|
+
3. **Output Example:**
|
|
28
|
+
|
|
29
|
+
Entry (en - English (US)): "///chunkStart/// - Here the translated content///chunkEnd///"
|
|
30
|
+
Expected Output (fr - French): "///chunkStart/// - Ici le contenu traduit///chunkEnd///"
|
|
31
|
+
|
|
32
|
+
# Custom instructions
|
|
33
|
+
|
|
34
|
+
{{customInstructions}}
|
|
35
|
+
|
|
36
|
+
# Application context
|
|
37
|
+
|
|
38
|
+
{{applicationContext}}
|
package/dist/esm/pull.mjs
CHANGED
|
@@ -30,7 +30,11 @@ const pull = async (options) => {
|
|
|
30
30
|
const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();
|
|
31
31
|
const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
|
|
32
32
|
const getDictionariesKeysResult = await intlayerAPI.dictionary.getDictionariesKeys({
|
|
33
|
-
|
|
33
|
+
...oAuth2AccessToken && {
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${oAuth2AccessToken}`
|
|
36
|
+
}
|
|
37
|
+
}
|
|
34
38
|
});
|
|
35
39
|
if (!getDictionariesKeysResult.data) {
|
|
36
40
|
throw new Error("No distant dictionaries found");
|
|
@@ -70,7 +74,11 @@ const pull = async (options) => {
|
|
|
70
74
|
statusObj.dictionaryKey,
|
|
71
75
|
void 0,
|
|
72
76
|
{
|
|
73
|
-
|
|
77
|
+
...oAuth2AccessToken && {
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${oAuth2AccessToken}`
|
|
80
|
+
}
|
|
81
|
+
}
|
|
74
82
|
}
|
|
75
83
|
);
|
|
76
84
|
const distantDictionary = getDictionaryResult.data;
|
package/dist/esm/pull.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pull.ts"],"sourcesContent":["import { getIntlayerAPI } from '@intlayer/api';\nimport {\n writeContentDeclaration,\n type DictionaryStatus,\n} from '@intlayer/chokidar';\nimport {\n getAppLogger,\n getConfiguration,\n GetConfigurationOptions,\n} from '@intlayer/config';\n\nimport type { Dictionary } from '@intlayer/core';\nimport pLimit from 'p-limit';\nimport * as readline from 'readline';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus;\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\nconst YELLOW = '\\x1b[33m';\nconst GREY_DARK = '\\x1b[90m';\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override);\n\n try {\n const config = getConfiguration(options?.configOptions);\n const { clientId, clientSecret } = config.editor;\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const intlayerAPI = getIntlayerAPI(undefined, config);\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.dictionary.getDictionariesKeys({\n headers: { Authorization: `Bearer ${oAuth2AccessToken}` },\n });\n\n if (!getDictionariesKeysResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesKeys: string[] = getDictionariesKeysResult.data;\n\n if (options?.dictionaries) {\n // Filter the dictionaries from the provided list of IDs\n distantDictionariesKeys = distantDictionariesKeys.filter(\n (dictionaryKey) => options.dictionaries!.includes(dictionaryKey)\n );\n }\n\n // Check if dictionaries list is empty\n if (distantDictionariesKeys.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] =\n distantDictionariesKeys.map((dictionaryKey, index) => ({\n dictionaryKey,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n }));\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'fetching';\n try {\n // Fetch the dictionary\n const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(\n statusObj.dictionaryKey,\n undefined,\n {\n headers: { Authorization: `Bearer ${oAuth2AccessToken}` },\n }\n );\n\n const distantDictionary = getDictionaryResult.data;\n\n if (!distantDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n distantDictionary,\n config,\n options?.newDictionariesPath\n );\n\n statusObj.status = status;\n\n successfullyFetchedDictionaries.push(distantDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n }\n };\n\n const fetchPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n\n await Promise.all(fetchPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n fetching: '', // Spinner handled separately\n 'up-to-date': '✔',\n updated: '✔',\n fetched: '✔',\n error: '✖',\n };\n return statusIcons[status] ?? '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'fetching') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'fetched' ||\n statusObj.status === 'imported' ||\n statusObj.status === 'updated' ||\n statusObj.status === 'up-to-date'\n ) {\n colorStart = GREEN;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'reimported in JSON' ||\n statusObj.status === 'reimported in new location'\n ) {\n colorStart = YELLOW;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'fetching') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/pull.ts"],"sourcesContent":["import { getIntlayerAPI } from '@intlayer/api';\nimport {\n writeContentDeclaration,\n type DictionaryStatus,\n} from '@intlayer/chokidar';\nimport {\n getAppLogger,\n getConfiguration,\n GetConfigurationOptions,\n} from '@intlayer/config';\n\nimport type { Dictionary } from '@intlayer/core';\nimport pLimit from 'p-limit';\nimport * as readline from 'readline';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus;\n icon: string;\n index: number;\n error?: Error;\n errorMessage?: string;\n spinnerFrameIndex?: number;\n};\n\nconst spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst RESET = '\\x1b[0m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst BLUE = '\\x1b[34m';\nconst GREY = '\\x1b[90m';\nconst YELLOW = '\\x1b[33m';\nconst GREY_DARK = '\\x1b[90m';\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override);\n\n try {\n const config = getConfiguration(options?.configOptions);\n const { clientId, clientSecret } = config.editor;\n\n if (!clientId || !clientSecret) {\n throw new Error(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.'\n );\n }\n\n const intlayerAPI = getIntlayerAPI(undefined, config);\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.dictionary.getDictionariesKeys({\n ...(oAuth2AccessToken && {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }),\n });\n\n if (!getDictionariesKeysResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesKeys: string[] = getDictionariesKeysResult.data;\n\n if (options?.dictionaries) {\n // Filter the dictionaries from the provided list of IDs\n distantDictionariesKeys = distantDictionariesKeys.filter(\n (dictionaryKey) => options.dictionaries!.includes(dictionaryKey)\n );\n }\n\n // Check if dictionaries list is empty\n if (distantDictionariesKeys.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] =\n distantDictionariesKeys.map((dictionaryKey, index) => ({\n dictionaryKey,\n icon: getStatusIcon('pending'),\n status: 'pending',\n index,\n spinnerFrameIndex: 0,\n }));\n\n // Output initial statuses\n for (const statusObj of dictionariesStatuses) {\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n\n // Start spinner timer\n const spinnerTimer = setInterval(() => {\n updateAllStatusLines(dictionariesStatuses);\n }, 100); // Update every 100ms\n\n // Process dictionaries in parallel with a concurrency limit\n const limit = pLimit(5); // Limit the number of concurrent requests\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'fetching';\n try {\n // Fetch the dictionary\n const getDictionaryResult = await intlayerAPI.dictionary.getDictionary(\n statusObj.dictionaryKey,\n undefined,\n {\n ...(oAuth2AccessToken && {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }),\n }\n );\n\n const distantDictionary = getDictionaryResult.data;\n\n if (!distantDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n distantDictionary,\n config,\n options?.newDictionariesPath\n );\n\n statusObj.status = status;\n\n successfullyFetchedDictionaries.push(distantDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n }\n };\n\n const fetchPromises = dictionariesStatuses.map((statusObj) =>\n limit(() => processDictionary(statusObj))\n );\n\n await Promise.all(fetchPromises);\n\n // Stop the spinner timer\n clearInterval(spinnerTimer);\n\n // Update statuses one last time\n updateAllStatusLines(dictionariesStatuses);\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst getStatusIcon = (status: string): string => {\n const statusIcons: Record<string, string> = {\n pending: '⏲',\n fetching: '', // Spinner handled separately\n 'up-to-date': '✔',\n updated: '✔',\n fetched: '✔',\n error: '✖',\n };\n return statusIcons[status] ?? '';\n};\n\nconst getStatusLine = (statusObj: DictionariesStatus): string => {\n let icon = getStatusIcon(statusObj.status);\n let colorStart = '';\n let colorEnd = '';\n\n if (statusObj.status === 'fetching') {\n // Use spinner frame\n icon = spinnerFrames[statusObj.spinnerFrameIndex! % spinnerFrames.length];\n colorStart = BLUE;\n colorEnd = RESET;\n } else if (statusObj.status === 'error') {\n colorStart = RED;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'fetched' ||\n statusObj.status === 'imported' ||\n statusObj.status === 'updated' ||\n statusObj.status === 'up-to-date'\n ) {\n colorStart = GREEN;\n colorEnd = RESET;\n } else if (\n statusObj.status === 'reimported in JSON' ||\n statusObj.status === 'reimported in new location'\n ) {\n colorStart = YELLOW;\n colorEnd = RESET;\n } else {\n colorStart = GREY;\n colorEnd = RESET;\n }\n\n return `- ${statusObj.dictionaryKey} ${GREY_DARK}[${colorStart}${icon}${statusObj.status}${GREY_DARK}]${colorEnd}`;\n};\n\nconst updateAllStatusLines = (dictionariesStatuses: DictionariesStatus[]) => {\n // Move cursor up to the first status line\n readline.moveCursor(process.stdout, 0, -dictionariesStatuses.length);\n for (const statusObj of dictionariesStatuses) {\n // Clear the line\n readline.clearLine(process.stdout, 0);\n\n if (statusObj.status === 'fetching') {\n // Update spinner frame\n statusObj.spinnerFrameIndex =\n (statusObj.spinnerFrameIndex! + 1) % spinnerFrames.length;\n }\n\n // Write the status line\n process.stdout.write(getStatusLine(statusObj) + '\\n');\n }\n};\n"],"mappings":"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","names":[]}
|
package/dist/esm/pushConfig.mjs
CHANGED
|
@@ -23,7 +23,11 @@ const pushConfig = async (options) => {
|
|
|
23
23
|
const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();
|
|
24
24
|
const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
|
|
25
25
|
const getDictionariesKeysResult = await intlayerAPI.project.pushProjectConfiguration(config, {
|
|
26
|
-
|
|
26
|
+
...oAuth2AccessToken && {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${oAuth2AccessToken}`
|
|
29
|
+
}
|
|
30
|
+
}
|
|
27
31
|
});
|
|
28
32
|
if (!getDictionariesKeysResult.data) {
|
|
29
33
|
throw new Error("Error pushing project configuration");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pushConfig.ts"],"sourcesContent":["import { getIntlayerAPI } from '@intlayer/api';\nimport {\n getAppLogger,\n getConfiguration,\n type GetConfigurationOptions,\n} from '@intlayer/config';\n\ntype PushOptions = {\n logPrefix?: string;\n configOptions?: GetConfigurationOptions;\n};\n\nexport const pushConfig = async (options?: PushOptions) => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n const { clientId, clientSecret } = config.editor;\n\n if (!clientId || !clientSecret) {\n appLogger(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.',\n {\n level: 'error',\n config: {\n prefix: options?.logPrefix,\n },\n }\n );\n return;\n }\n\n const intlayerAPI = getIntlayerAPI(undefined, config);\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.project.pushProjectConfiguration(config, {\n headers: {
|
|
1
|
+
{"version":3,"sources":["../../src/pushConfig.ts"],"sourcesContent":["import { getIntlayerAPI } from '@intlayer/api';\nimport {\n getAppLogger,\n getConfiguration,\n type GetConfigurationOptions,\n} from '@intlayer/config';\n\ntype PushOptions = {\n logPrefix?: string;\n configOptions?: GetConfigurationOptions;\n};\n\nexport const pushConfig = async (options?: PushOptions) => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n const { clientId, clientSecret } = config.editor;\n\n if (!clientId || !clientSecret) {\n appLogger(\n 'Missing OAuth2 client ID or client secret. To get access token go to https://intlayer.org/dashboard/project.',\n {\n level: 'error',\n config: {\n prefix: options?.logPrefix,\n },\n }\n );\n return;\n }\n\n const intlayerAPI = getIntlayerAPI(undefined, config);\n\n const oAuth2TokenResult = await intlayerAPI.auth.getOAuth2AccessToken();\n\n const oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await intlayerAPI.project.pushProjectConfiguration(config, {\n ...(oAuth2AccessToken && {\n headers: {\n Authorization: `Bearer ${oAuth2AccessToken}`,\n },\n }),\n });\n\n if (!getDictionariesKeysResult.data) {\n throw new Error('Error pushing project configuration');\n }\n\n appLogger('Project configuration pushed successfully', {\n config: {\n prefix: options?.logPrefix,\n },\n });\n\n appLogger(JSON.stringify(getDictionariesKeysResult.data, null, 2), {\n config: {\n prefix: options?.logPrefix,\n },\n });\n};\n"],"mappings":"AAAA,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAOA,MAAM,aAAa,OAAO,YAA0B;AACzD,QAAM,SAAS,iBAAiB,SAAS,aAAa;AACtD,QAAM,YAAY,aAAa,MAAM;AAErC,QAAM,EAAE,UAAU,aAAa,IAAI,OAAO;AAE1C,MAAI,CAAC,YAAY,CAAC,cAAc;AAC9B;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,QAAQ;AAAA,UACN,QAAQ,SAAS;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,cAAc,eAAe,QAAW,MAAM;AAEpD,QAAM,oBAAoB,MAAM,YAAY,KAAK,qBAAqB;AAEtE,QAAM,oBAAoB,kBAAkB,MAAM;AAGlD,QAAM,4BACJ,MAAM,YAAY,QAAQ,yBAAyB,QAAQ;AAAA,IACzD,GAAI,qBAAqB;AAAA,MACvB,SAAS;AAAA,QACP,eAAe,UAAU,iBAAiB;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,CAAC;AAEH,MAAI,CAAC,0BAA0B,MAAM;AACnC,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,YAAU,6CAA6C;AAAA,IACrD,QAAQ;AAAA,MACN,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF,CAAC;AAED,YAAU,KAAK,UAAU,0BAA0B,MAAM,MAAM,CAAC,GAAG;AAAA,IACjE,QAAQ;AAAA,MACN,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getAuthAPI } from "@intlayer/api";
|
|
2
|
+
import {
|
|
3
|
+
getAppLogger,
|
|
4
|
+
getConfiguration,
|
|
5
|
+
Locales,
|
|
6
|
+
retryManager
|
|
7
|
+
} from "@intlayer/config";
|
|
8
|
+
import { getLocaleName } from "@intlayer/core";
|
|
9
|
+
import fg from "fast-glob";
|
|
10
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
11
|
+
import { readFile } from "fs/promises";
|
|
12
|
+
import pLimit from "p-limit";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { chunkText } from "./utils/calculateChunks.mjs";
|
|
16
|
+
import { checkAIAccess } from "./utils/checkAIAccess.mjs";
|
|
17
|
+
import { checkFileModifiedRange } from "./utils/checkFileModifiedRange.mjs";
|
|
18
|
+
import { chunkInference } from "./utils/chunkInference.mjs";
|
|
19
|
+
import { fixChunkStartEndChars } from "./utils/fixChunkStartEndChars.mjs";
|
|
20
|
+
import { getChunk } from "./utils/getChunk.mjs";
|
|
21
|
+
import { getOutputFilePath } from "./utils/getOutputFilePath.mjs";
|
|
22
|
+
const isESModule = typeof import.meta.url === "string";
|
|
23
|
+
const dir = isESModule ? dirname(fileURLToPath(import.meta.url)) : __dirname;
|
|
24
|
+
const reviewFile = async (baseFilePath, outputFilePath, locale, baseLocale, aiOptions, configOptions, oAuth2AccessToken, customInstructions) => {
|
|
25
|
+
try {
|
|
26
|
+
const configuration = getConfiguration(configOptions);
|
|
27
|
+
const appLogger = getAppLogger(configuration);
|
|
28
|
+
const basedFileContent = await readFile(baseFilePath, "utf-8");
|
|
29
|
+
const fileToReviewContent = await readFile(outputFilePath, "utf-8");
|
|
30
|
+
let updatedFileContent = fileToReviewContent;
|
|
31
|
+
let fileResultContent = "";
|
|
32
|
+
const basePrompt = (await readFile(join(dir, "./prompts/REVIEW_PROMPT.md"), "utf-8")).replaceAll(
|
|
33
|
+
"{{localeName}}",
|
|
34
|
+
`${getLocaleName(locale, Locales.ENGLISH)} (${locale})`
|
|
35
|
+
).replaceAll(
|
|
36
|
+
"{{baseLocaleName}}",
|
|
37
|
+
`${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale})`
|
|
38
|
+
).replace("{{applicationContext}}", aiOptions?.applicationContext ?? "-").replace("{{customInstructions}}", customInstructions ?? "-");
|
|
39
|
+
const baseChunks = chunkText(basedFileContent, 800, 0);
|
|
40
|
+
appLogger(` Base file splitted into ${baseChunks.length} chunks`);
|
|
41
|
+
for (let i = 0; i < baseChunks.length; i++) {
|
|
42
|
+
const baseChunkContext = baseChunks[i];
|
|
43
|
+
const getBaseChunkContextPrompt = () => `**CHUNK ${i + 1} to ${Math.min(i + 3, baseChunks.length)} of ${baseChunks.length}** is the base chunk in ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale}) as reference.
|
|
44
|
+
///chunksStart///` + (baseChunks[i - 1]?.content ?? "") + baseChunkContext.content + (baseChunks[i + 1]?.content ?? "") + `///chunksEnd///`;
|
|
45
|
+
const getChunkToReviewPrompt = () => `**CHUNK ${i + 1} to ${Math.min(i + 3, baseChunks.length)} of ${baseChunks.length}** is the current chunk to review in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}) as reference.
|
|
46
|
+
///chunksStart///` + getChunk(updatedFileContent, {
|
|
47
|
+
lineStart: baseChunks[i - 1]?.lineStart ?? 0,
|
|
48
|
+
lineLength: (baseChunks[i - 1]?.lineLength ?? 0) + baseChunkContext.lineLength + (baseChunks[i + 1]?.lineLength ?? 0)
|
|
49
|
+
}) + `///chunksEnd///`;
|
|
50
|
+
let reviewedChunkResult = await retryManager(async () => {
|
|
51
|
+
const result = await chunkInference(
|
|
52
|
+
[
|
|
53
|
+
{ role: "system", content: basePrompt },
|
|
54
|
+
{ role: "system", content: getBaseChunkContextPrompt() },
|
|
55
|
+
{ role: "system", content: getChunkToReviewPrompt() },
|
|
56
|
+
{
|
|
57
|
+
role: "system",
|
|
58
|
+
content: `The next user message will be the **CHUNK ${i + 1} of ${baseChunks.length}** that should be translated in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}).`
|
|
59
|
+
},
|
|
60
|
+
{ role: "user", content: baseChunkContext.content }
|
|
61
|
+
],
|
|
62
|
+
aiOptions,
|
|
63
|
+
oAuth2AccessToken
|
|
64
|
+
);
|
|
65
|
+
appLogger(
|
|
66
|
+
` -> ${result.tokenUsed} tokens used - CHUNK ${i + 1} of ${baseChunks.length}`
|
|
67
|
+
);
|
|
68
|
+
const fixedReviewedChunkResult = fixChunkStartEndChars(
|
|
69
|
+
result?.fileContent,
|
|
70
|
+
baseChunkContext.content
|
|
71
|
+
);
|
|
72
|
+
return fixedReviewedChunkResult;
|
|
73
|
+
})();
|
|
74
|
+
updatedFileContent = updatedFileContent.replace(
|
|
75
|
+
baseChunkContext.content,
|
|
76
|
+
reviewedChunkResult
|
|
77
|
+
);
|
|
78
|
+
fileResultContent += reviewedChunkResult;
|
|
79
|
+
}
|
|
80
|
+
mkdirSync(dirname(outputFilePath), { recursive: true });
|
|
81
|
+
writeFileSync(outputFilePath, fileResultContent);
|
|
82
|
+
appLogger(` File ${outputFilePath} created/updated successfully.`);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(error);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const reviewDoc = async ({
|
|
88
|
+
docPattern,
|
|
89
|
+
locales,
|
|
90
|
+
excludedGlobPattern,
|
|
91
|
+
baseLocale,
|
|
92
|
+
aiOptions,
|
|
93
|
+
nbSimultaneousFileProcessed,
|
|
94
|
+
configOptions,
|
|
95
|
+
customInstructions,
|
|
96
|
+
skipIfModifiedBefore,
|
|
97
|
+
skipIfModifiedAfter
|
|
98
|
+
}) => {
|
|
99
|
+
const configuration = getConfiguration(configOptions);
|
|
100
|
+
const appLogger = getAppLogger(configuration);
|
|
101
|
+
if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {
|
|
102
|
+
appLogger(
|
|
103
|
+
`Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`
|
|
104
|
+
);
|
|
105
|
+
nbSimultaneousFileProcessed = 10;
|
|
106
|
+
}
|
|
107
|
+
const limit = pLimit(nbSimultaneousFileProcessed ?? 3);
|
|
108
|
+
const docList = fg.sync(docPattern, {
|
|
109
|
+
ignore: excludedGlobPattern
|
|
110
|
+
});
|
|
111
|
+
checkAIAccess(configuration, aiOptions);
|
|
112
|
+
let oAuth2AccessToken;
|
|
113
|
+
if (configuration.editor.clientId) {
|
|
114
|
+
const intlayerAuthAPI = getAuthAPI(void 0, configuration);
|
|
115
|
+
const oAuth2TokenResult = await intlayerAuthAPI.getOAuth2AccessToken();
|
|
116
|
+
oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
|
|
117
|
+
}
|
|
118
|
+
appLogger(
|
|
119
|
+
`Base locale is ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale})`
|
|
120
|
+
);
|
|
121
|
+
appLogger(
|
|
122
|
+
`Reviewing ${locales.length} locales: [ ${locales.map((locale) => `${getLocaleName(locale, Locales.ENGLISH)} (${locale})`).join(", ")} ]`
|
|
123
|
+
);
|
|
124
|
+
appLogger(`Reviewing ${docList.length} files:`);
|
|
125
|
+
appLogger(docList.map((path) => ` - ${path}
|
|
126
|
+
`));
|
|
127
|
+
const tasks = docList.map(
|
|
128
|
+
(docPath) => locales.flatMap(
|
|
129
|
+
(locale) => limit(async () => {
|
|
130
|
+
appLogger(
|
|
131
|
+
`Reviewing file: ${docPath} to ${getLocaleName(
|
|
132
|
+
locale,
|
|
133
|
+
Locales.ENGLISH
|
|
134
|
+
)} (${locale})`
|
|
135
|
+
);
|
|
136
|
+
const absoluteBaseFilePath = join(
|
|
137
|
+
configuration.content.baseDir,
|
|
138
|
+
docPath
|
|
139
|
+
);
|
|
140
|
+
const outputFilePath = getOutputFilePath(
|
|
141
|
+
absoluteBaseFilePath,
|
|
142
|
+
locale,
|
|
143
|
+
baseLocale
|
|
144
|
+
);
|
|
145
|
+
const fileModificationData = checkFileModifiedRange(outputFilePath, {
|
|
146
|
+
skipIfModifiedBefore,
|
|
147
|
+
skipIfModifiedAfter
|
|
148
|
+
});
|
|
149
|
+
if (fileModificationData.isSkipped) {
|
|
150
|
+
appLogger(fileModificationData.message);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
await reviewFile(
|
|
154
|
+
absoluteBaseFilePath,
|
|
155
|
+
outputFilePath,
|
|
156
|
+
locale,
|
|
157
|
+
baseLocale,
|
|
158
|
+
aiOptions,
|
|
159
|
+
configOptions,
|
|
160
|
+
oAuth2AccessToken,
|
|
161
|
+
customInstructions
|
|
162
|
+
);
|
|
163
|
+
})
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
await Promise.all(tasks);
|
|
167
|
+
};
|
|
168
|
+
export {
|
|
169
|
+
reviewDoc,
|
|
170
|
+
reviewFile
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=reviewDoc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/reviewDoc.ts"],"sourcesContent":["import { AIOptions, getAuthAPI } from '@intlayer/api'; // Importing only getAiAPI for now\nimport {\n getAppLogger,\n getConfiguration,\n GetConfigurationOptions,\n Locales,\n retryManager,\n} from '@intlayer/config';\nimport { getLocaleName } from '@intlayer/core';\nimport fg from 'fast-glob';\nimport { mkdirSync, writeFileSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport pLimit from 'p-limit';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { chunkText } from './utils/calculateChunks';\nimport { checkAIAccess } from './utils/checkAIAccess';\nimport { checkFileModifiedRange } from './utils/checkFileModifiedRange';\nimport { chunkInference } from './utils/chunkInference';\nimport { fixChunkStartEndChars } from './utils/fixChunkStartEndChars';\nimport { getChunk } from './utils/getChunk';\nimport { getOutputFilePath } from './utils/getOutputFilePath';\n\nconst isESModule = typeof import.meta.url === 'string';\n\nconst dir = isESModule ? dirname(fileURLToPath(import.meta.url)) : __dirname;\n\n/**\n * Translate a single file for a given locale\n */\nexport const reviewFile = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locales,\n baseLocale: Locales,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n oAuth2AccessToken?: string,\n customInstructions?: string\n) => {\n try {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n const basedFileContent = await readFile(baseFilePath, 'utf-8');\n const fileToReviewContent = await readFile(outputFilePath, 'utf-8');\n\n let updatedFileContent = fileToReviewContent;\n let fileResultContent = '';\n\n // Prepare the base prompt for ChatGPT\n const basePrompt = (\n await readFile(join(dir, './prompts/REVIEW_PROMPT.md'), 'utf-8')\n )\n .replaceAll(\n '{{localeName}}',\n `${getLocaleName(locale, Locales.ENGLISH)} (${locale})`\n )\n .replaceAll(\n '{{baseLocaleName}}',\n `${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale})`\n )\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const baseChunks = chunkText(basedFileContent, 800, 0);\n\n appLogger(` Base file splitted into ${baseChunks.length} chunks`);\n\n for (let i = 0; i < baseChunks.length; i++) {\n const baseChunkContext = baseChunks[i];\n\n const getBaseChunkContextPrompt = () =>\n `**CHUNK ${i + 1} to ${Math.min(i + 3, baseChunks.length)} of ${baseChunks.length}** is the base chunk in ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale}) as reference.\\n` +\n `///chunksStart///` +\n (baseChunks[i - 1]?.content ?? '') +\n baseChunkContext.content +\n (baseChunks[i + 1]?.content ?? '') +\n `///chunksEnd///`;\n\n const getChunkToReviewPrompt = () =>\n `**CHUNK ${i + 1} to ${Math.min(i + 3, baseChunks.length)} of ${baseChunks.length}** is the current chunk to review in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}) as reference.\\n` +\n `///chunksStart///` +\n getChunk(updatedFileContent, {\n lineStart: baseChunks[i - 1]?.lineStart ?? 0,\n lineLength:\n (baseChunks[i - 1]?.lineLength ?? 0) +\n baseChunkContext.lineLength +\n (baseChunks[i + 1]?.lineLength ?? 0),\n }) +\n `///chunksEnd///`;\n\n // Make the actual translation call\n let reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getChunkToReviewPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **CHUNK ${i + 1} of ${baseChunks.length}** that should be translated in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}).`,\n },\n { role: 'user', content: baseChunkContext.content },\n ],\n aiOptions,\n oAuth2AccessToken\n );\n\n appLogger(\n ` -> ${result.tokenUsed} tokens used - CHUNK ${i + 1} of ${baseChunks.length}`\n );\n\n const fixedReviewedChunkResult = fixChunkStartEndChars(\n result?.fileContent,\n baseChunkContext.content\n );\n\n return fixedReviewedChunkResult;\n })();\n\n updatedFileContent = updatedFileContent.replace(\n baseChunkContext.content,\n reviewedChunkResult\n );\n\n fileResultContent += reviewedChunkResult;\n }\n\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, fileResultContent);\n\n appLogger(` File ${outputFilePath} created/updated successfully.`);\n } catch (error) {\n console.error(error);\n }\n};\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locales[];\n excludedGlobPattern: string[];\n baseLocale: Locales;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n const limit = pLimit(nbSimultaneousFileProcessed ?? 3);\n\n const docList: string[] = fg.sync(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n checkAIAccess(configuration, aiOptions);\n\n let oAuth2AccessToken: string | undefined;\n if (configuration.editor.clientId) {\n const intlayerAuthAPI = getAuthAPI(undefined, configuration);\n const oAuth2TokenResult = await intlayerAuthAPI.getOAuth2AccessToken();\n\n oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n }\n\n appLogger(\n `Base locale is ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale})`\n );\n appLogger(\n `Reviewing ${locales.length} locales: [ ${locales\n .map((locale) => `${getLocaleName(locale, Locales.ENGLISH)} (${locale})`)\n .join(', ')} ]`\n );\n\n appLogger(`Reviewing ${docList.length} files:`);\n appLogger(docList.map((path) => ` - ${path}\\n`));\n\n const tasks = docList.map((docPath) =>\n locales.flatMap((locale) =>\n limit(async () => {\n appLogger(\n `Reviewing file: ${docPath} to ${getLocaleName(\n locale,\n Locales.ENGLISH\n )} (${locale})`\n );\n\n const absoluteBaseFilePath = join(\n configuration.content.baseDir,\n docPath\n );\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n\n await reviewFile(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locales,\n baseLocale,\n aiOptions,\n configOptions,\n oAuth2AccessToken,\n customInstructions\n );\n })\n )\n );\n\n await Promise.all(tasks);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { getAuthAPI } from "@intlayer/api";
|
|
2
|
+
import {
|
|
3
|
+
getAppLogger,
|
|
4
|
+
getConfiguration,
|
|
5
|
+
Locales,
|
|
6
|
+
retryManager
|
|
7
|
+
} from "@intlayer/config";
|
|
8
|
+
import { getLocaleName } from "@intlayer/core";
|
|
9
|
+
import fg from "fast-glob";
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
11
|
+
import { readFile } from "fs/promises";
|
|
12
|
+
import pLimit from "p-limit";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { chunkText } from "./utils/calculateChunks.mjs";
|
|
16
|
+
import { checkAIAccess } from "./utils/checkAIAccess.mjs";
|
|
17
|
+
import { checkFileModifiedRange } from "./utils/checkFileModifiedRange.mjs";
|
|
18
|
+
import { chunkInference } from "./utils/chunkInference.mjs";
|
|
19
|
+
import { fixChunkStartEndChars } from "./utils/fixChunkStartEndChars.mjs";
|
|
20
|
+
import { getChunk } from "./utils/getChunk.mjs";
|
|
21
|
+
import { getOutputFilePath } from "./utils/getOutputFilePath.mjs";
|
|
22
|
+
const isESModule = typeof import.meta.url === "string";
|
|
23
|
+
const dir = isESModule ? dirname(fileURLToPath(import.meta.url)) : __dirname;
|
|
24
|
+
const translateFile = async (baseFilePath, outputFilePath, locale, baseLocale, aiOptions, configOptions, oAuth2AccessToken, customInstructions) => {
|
|
25
|
+
try {
|
|
26
|
+
const configuration = getConfiguration(configOptions);
|
|
27
|
+
const appLogger = getAppLogger(configuration);
|
|
28
|
+
const fileContent = await readFile(baseFilePath, "utf-8");
|
|
29
|
+
let fileResultContent = fileContent;
|
|
30
|
+
const basePrompt = (await readFile(join(dir, "./prompts/TRANSLATE_PROMPT.md"), "utf-8")).replaceAll(
|
|
31
|
+
"{{localeName}}",
|
|
32
|
+
`${getLocaleName(locale, Locales.ENGLISH)} (${locale})`
|
|
33
|
+
).replaceAll(
|
|
34
|
+
"{{baseLocaleName}}",
|
|
35
|
+
`${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale})`
|
|
36
|
+
).replace("{{applicationContext}}", aiOptions?.applicationContext ?? "-").replace("{{customInstructions}}", customInstructions ?? "-");
|
|
37
|
+
const chunks = chunkText(fileContent);
|
|
38
|
+
appLogger(`Base file splitted into ${chunks.length} chunks`);
|
|
39
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
40
|
+
const isFirstChunk = i === 0;
|
|
41
|
+
const getPrevChunkPrompt = () => `**CHUNK ${i} of ${chunks.length}** that has been translated in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}):
|
|
42
|
+
///chunkStart///` + getChunk(fileResultContent, chunks[i - 1]) + `///chunkEnd///`;
|
|
43
|
+
const getBaseChunkContextPrompt = () => `**CHUNK ${i + 1} to ${Math.min(i + 3, chunks.length)} of ${chunks.length}** is the base chunk in ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale}) as reference.
|
|
44
|
+
///chunksStart///` + (chunks[i - 1]?.content ?? "") + chunks[i].content + (chunks[i + 1]?.content ?? "") + `///chunksEnd///`;
|
|
45
|
+
const fileToTranslateCurrentChunk = chunks[i].content;
|
|
46
|
+
let chunkTranslation = await retryManager(async () => {
|
|
47
|
+
const result = await chunkInference(
|
|
48
|
+
[
|
|
49
|
+
{ role: "system", content: basePrompt },
|
|
50
|
+
{ role: "system", content: getBaseChunkContextPrompt() },
|
|
51
|
+
...isFirstChunk ? [] : [{ role: "system", content: getPrevChunkPrompt() }],
|
|
52
|
+
{
|
|
53
|
+
role: "system",
|
|
54
|
+
content: `The next user message will be the **CHUNK ${i + 1} of ${chunks.length}** in ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale}) to translate in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}):`
|
|
55
|
+
},
|
|
56
|
+
{ role: "user", content: fileToTranslateCurrentChunk }
|
|
57
|
+
],
|
|
58
|
+
aiOptions,
|
|
59
|
+
oAuth2AccessToken
|
|
60
|
+
);
|
|
61
|
+
appLogger(
|
|
62
|
+
` -> ${result.tokenUsed} tokens used - CHUNK ${i + 1} of ${chunks.length}`
|
|
63
|
+
);
|
|
64
|
+
const fixedTranslatedChunkResult = fixChunkStartEndChars(
|
|
65
|
+
result?.fileContent,
|
|
66
|
+
fileToTranslateCurrentChunk
|
|
67
|
+
);
|
|
68
|
+
return fixedTranslatedChunkResult;
|
|
69
|
+
})();
|
|
70
|
+
fileResultContent = fileResultContent.replace(
|
|
71
|
+
fileToTranslateCurrentChunk,
|
|
72
|
+
chunkTranslation
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
mkdirSync(dirname(outputFilePath), { recursive: true });
|
|
76
|
+
writeFileSync(outputFilePath, fileResultContent);
|
|
77
|
+
appLogger(`File ${outputFilePath} created/updated successfully.`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(error);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const translateDoc = async ({
|
|
83
|
+
docPattern,
|
|
84
|
+
locales,
|
|
85
|
+
excludedGlobPattern,
|
|
86
|
+
baseLocale,
|
|
87
|
+
aiOptions,
|
|
88
|
+
nbSimultaneousFileProcessed,
|
|
89
|
+
configOptions,
|
|
90
|
+
customInstructions,
|
|
91
|
+
skipIfModifiedBefore,
|
|
92
|
+
skipIfModifiedAfter
|
|
93
|
+
}) => {
|
|
94
|
+
const configuration = getConfiguration(configOptions);
|
|
95
|
+
const appLogger = getAppLogger(configuration);
|
|
96
|
+
if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {
|
|
97
|
+
appLogger(
|
|
98
|
+
`Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`
|
|
99
|
+
);
|
|
100
|
+
nbSimultaneousFileProcessed = 10;
|
|
101
|
+
}
|
|
102
|
+
const limit = pLimit(nbSimultaneousFileProcessed ?? 3);
|
|
103
|
+
const docList = fg.sync(docPattern, {
|
|
104
|
+
ignore: excludedGlobPattern
|
|
105
|
+
});
|
|
106
|
+
checkAIAccess(configuration, aiOptions);
|
|
107
|
+
let oAuth2AccessToken;
|
|
108
|
+
if (configuration.editor.clientId) {
|
|
109
|
+
const intlayerAuthAPI = getAuthAPI(void 0, configuration);
|
|
110
|
+
const oAuth2TokenResult = await intlayerAuthAPI.getOAuth2AccessToken();
|
|
111
|
+
oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;
|
|
112
|
+
}
|
|
113
|
+
appLogger(`Base locale is ${getLocaleName(baseLocale)} (${baseLocale})`);
|
|
114
|
+
appLogger(
|
|
115
|
+
`Translating ${locales.length} locales: [ ${locales.map((locale) => `${getLocaleName(locale, baseLocale)} (${locale})`).join(", ")} ]`
|
|
116
|
+
);
|
|
117
|
+
appLogger(`Translating ${docList.length} files:`);
|
|
118
|
+
appLogger(docList.map((path) => ` - ${path}
|
|
119
|
+
`));
|
|
120
|
+
const tasks = docList.map(
|
|
121
|
+
(docPath) => locales.flatMap(
|
|
122
|
+
(locale) => limit(async () => {
|
|
123
|
+
appLogger(
|
|
124
|
+
`Translating file: ${docPath} to ${getLocaleName(
|
|
125
|
+
locale,
|
|
126
|
+
Locales.ENGLISH
|
|
127
|
+
)} (${locale})`
|
|
128
|
+
);
|
|
129
|
+
const absoluteBaseFilePath = join(
|
|
130
|
+
configuration.content.baseDir,
|
|
131
|
+
docPath
|
|
132
|
+
);
|
|
133
|
+
const outputFilePath = getOutputFilePath(
|
|
134
|
+
absoluteBaseFilePath,
|
|
135
|
+
locale,
|
|
136
|
+
baseLocale
|
|
137
|
+
);
|
|
138
|
+
if (!existsSync(outputFilePath)) {
|
|
139
|
+
appLogger(`File ${outputFilePath} does not exist, creating it...`);
|
|
140
|
+
mkdirSync(dirname(outputFilePath), { recursive: true });
|
|
141
|
+
writeFileSync(outputFilePath, "");
|
|
142
|
+
}
|
|
143
|
+
const fileModificationData = checkFileModifiedRange(outputFilePath, {
|
|
144
|
+
skipIfModifiedBefore,
|
|
145
|
+
skipIfModifiedAfter
|
|
146
|
+
});
|
|
147
|
+
if (fileModificationData.isSkipped) {
|
|
148
|
+
appLogger(fileModificationData.message);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
await translateFile(
|
|
152
|
+
absoluteBaseFilePath,
|
|
153
|
+
outputFilePath,
|
|
154
|
+
locale,
|
|
155
|
+
baseLocale,
|
|
156
|
+
aiOptions,
|
|
157
|
+
configOptions,
|
|
158
|
+
oAuth2AccessToken,
|
|
159
|
+
customInstructions
|
|
160
|
+
);
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
await Promise.all(tasks);
|
|
165
|
+
};
|
|
166
|
+
export {
|
|
167
|
+
translateDoc,
|
|
168
|
+
translateFile
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=translateDoc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/translateDoc.ts"],"sourcesContent":["import { AIOptions, getAuthAPI } from '@intlayer/api';\nimport {\n getAppLogger,\n getConfiguration,\n GetConfigurationOptions,\n Locales,\n retryManager,\n} from '@intlayer/config';\nimport { getLocaleName } from '@intlayer/core';\nimport fg from 'fast-glob';\nimport { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport pLimit from 'p-limit';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { chunkText } from './utils/calculateChunks';\nimport { checkAIAccess } from './utils/checkAIAccess';\nimport { checkFileModifiedRange } from './utils/checkFileModifiedRange';\nimport { chunkInference } from './utils/chunkInference';\nimport { fixChunkStartEndChars } from './utils/fixChunkStartEndChars';\nimport { getChunk } from './utils/getChunk';\nimport { getOutputFilePath } from './utils/getOutputFilePath';\n\nconst isESModule = typeof import.meta.url === 'string';\n\nconst dir = isESModule ? dirname(fileURLToPath(import.meta.url)) : __dirname;\n\n/**\n * Translate a single file for a given locale\n */\nexport const translateFile = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locales,\n baseLocale: Locales,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n oAuth2AccessToken?: string,\n customInstructions?: string\n) => {\n try {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n // Determine the target locale file path\n const fileContent = await readFile(baseFilePath, 'utf-8');\n let fileResultContent = fileContent;\n\n // Prepare the base prompt for ChatGPT\n const basePrompt = (\n await readFile(join(dir, './prompts/TRANSLATE_PROMPT.md'), 'utf-8')\n )\n .replaceAll(\n '{{localeName}}',\n `${getLocaleName(locale, Locales.ENGLISH)} (${locale})`\n )\n .replaceAll(\n '{{baseLocaleName}}',\n `${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale})`\n )\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n // 1. Chunk the file by number of lines instead of characters\n const chunks = chunkText(fileContent);\n appLogger(`Base file splitted into ${chunks.length} chunks`);\n\n for (let i = 0; i < chunks.length; i++) {\n const isFirstChunk = i === 0;\n\n // Build the chunk-specific prompt\n const getPrevChunkPrompt = () =>\n `**CHUNK ${i} of ${chunks.length}** that has been translated in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}):\\n` +\n `///chunkStart///` +\n getChunk(fileResultContent, chunks[i - 1]) +\n `///chunkEnd///`;\n\n const getBaseChunkContextPrompt = () =>\n `**CHUNK ${i + 1} to ${Math.min(i + 3, chunks.length)} of ${chunks.length}** is the base chunk in ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale}) as reference.\\n` +\n `///chunksStart///` +\n (chunks[i - 1]?.content ?? '') +\n chunks[i].content +\n (chunks[i + 1]?.content ?? '') +\n `///chunksEnd///`;\n\n const fileToTranslateCurrentChunk = chunks[i].content;\n\n // Make the actual translation call\n let chunkTranslation = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n\n { role: 'system', content: getBaseChunkContextPrompt() },\n ...(isFirstChunk\n ? []\n : [{ role: 'system', content: getPrevChunkPrompt() } as const]),\n {\n role: 'system',\n content: `The next user message will be the **CHUNK ${i + 1} of ${chunks.length}** in ${getLocaleName(baseLocale, Locales.ENGLISH)} (${baseLocale}) to translate in ${getLocaleName(locale, Locales.ENGLISH)} (${locale}):`,\n },\n { role: 'user', content: fileToTranslateCurrentChunk },\n ],\n aiOptions,\n oAuth2AccessToken\n );\n\n appLogger(\n ` -> ${result.tokenUsed} tokens used - CHUNK ${i + 1} of ${chunks.length}`\n );\n\n const fixedTranslatedChunkResult = fixChunkStartEndChars(\n result?.fileContent,\n fileToTranslateCurrentChunk\n );\n\n return fixedTranslatedChunkResult;\n })();\n\n // Replace the chunk in the file content\n fileResultContent = fileResultContent.replace(\n fileToTranslateCurrentChunk,\n chunkTranslation\n );\n }\n\n // 4. Write the final translation to the appropriate file path\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, fileResultContent);\n\n appLogger(`File ${outputFilePath} created/updated successfully.`);\n } catch (error) {\n console.error(error);\n }\n};\n\ntype TranslateDocOptions = {\n docPattern: string[];\n locales: Locales[];\n excludedGlobPattern: string[];\n baseLocale: Locales;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n};\n\n/**\n * Main translate function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then translates them to each locale in LOCALE_LIST.\n */\nexport const translateDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n}: TranslateDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n const limit = pLimit(nbSimultaneousFileProcessed ?? 3);\n\n const docList: string[] = fg.sync(docPattern, {\n ignore: excludedGlobPattern,\n });\n\n checkAIAccess(configuration, aiOptions);\n\n let oAuth2AccessToken: string | undefined;\n if (configuration.editor.clientId) {\n const intlayerAuthAPI = getAuthAPI(undefined, configuration);\n const oAuth2TokenResult = await intlayerAuthAPI.getOAuth2AccessToken();\n\n oAuth2AccessToken = oAuth2TokenResult.data?.accessToken;\n }\n\n appLogger(`Base locale is ${getLocaleName(baseLocale)} (${baseLocale})`);\n appLogger(\n `Translating ${locales.length} locales: [ ${locales\n .map((locale) => `${getLocaleName(locale, baseLocale)} (${locale})`)\n .join(', ')} ]`\n );\n\n appLogger(`Translating ${docList.length} files:`);\n appLogger(docList.map((path) => ` - ${path}\\n`));\n\n const tasks = docList.map((docPath) =>\n locales.flatMap((locale) =>\n limit(async () => {\n appLogger(\n `Translating file: ${docPath} to ${getLocaleName(\n locale,\n Locales.ENGLISH\n )} (${locale})`\n );\n\n const absoluteBaseFilePath = join(\n configuration.content.baseDir,\n docPath\n );\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // check if the file exist, otherwise create it\n if (!existsSync(outputFilePath)) {\n appLogger(`File ${outputFilePath} does not exist, creating it...`);\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, '');\n }\n\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n\n await translateFile(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locales,\n baseLocale,\n aiOptions,\n configOptions,\n oAuth2AccessToken,\n customInstructions\n );\n })\n )\n );\n\n await Promise.all(tasks);\n};\n"],"mappings":"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","names":[]}
|