document | deepl-markdown engine & tests
This commit is contained in:
parent
ef493ae64a
commit
431203c542
293081
packages/i18n/dist-node/main_node.cjs
Normal file
293081
packages/i18n/dist-node/main_node.cjs
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
packages/i18n/dist/commands/glossary.js
vendored
2
packages/i18n/dist/commands/glossary.js
vendored
@ -89,4 +89,4 @@ export const register = (cli) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2xvc3NhcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tbWFuZHMvZ2xvc3NhcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFFNUIsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQzFDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDckMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2xELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUVyRCxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQ3ZDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDekIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsZ0JBQWdCO1FBQzFCLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLE9BQU8sRUFBRSxZQUFZO1FBQ3JCLFFBQVEsRUFBRSxxQ0FBcUM7S0FDbEQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsdURBQXVEO0tBQ3BFLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFO1FBQ2IsUUFBUSxFQUFFLGFBQWE7S0FDMUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsd0RBQXdEO1FBQ2xFLE9BQU8sRUFBRSw4Q0FBOEM7S0FDMUQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsUUFBUSxFQUFFLDRFQUE0RTtRQUN0RixPQUFPLEVBQUUsRUFBRTtLQUNkLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFO1FBQ2YsUUFBUSxFQUFFLGlCQUFpQjtRQUMzQixPQUFPLEVBQUUseUJBQXlCO0tBQ3JDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLFFBQVEsRUFBRSw0RUFBNEU7UUFDdEYsT0FBTyxFQUFFLElBQUk7S0FDaEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7UUFDZCxRQUFRLEVBQUUsc0JBQXNCO1FBQ2hDLE9BQU8sRUFBRSxnQkFBZ0I7S0FDNUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFBO0FBRUQsSUFBSSxPQUFPLEdBQUcsQ0FBQyxLQUFlLEVBQUUsRUFBRSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQUd4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFhLEVBQUUsRUFBRTtJQUN0QyxPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsNkJBQTZCLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFtQixFQUFHLEVBQUU7UUFDekcsUUFBUSxFQUFFLENBQUE7UUFFVixJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFDLE9BQU07UUFBQyxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxHQUFRLElBQUksQ0FBQTtRQUN0QixNQUFNLE1BQU0sR0FBUSxjQUFjLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2hELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUE7UUFDdEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O2NBbUNFO1FBQ04sQ0FBQztRQUNELElBQUksSUFBSSxLQUFLLE9BQU8sSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDL0IsTUFBTSxLQUFLLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBYSxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ3hFLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQSJ9
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2xvc3NhcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tbWFuZHMvZ2xvc3NhcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFFNUIsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQzFDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDckMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2xELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUVyRCxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQ3ZDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDekIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsZ0JBQWdCO1FBQzFCLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLE9BQU8sRUFBRSxZQUFZO1FBQ3JCLFFBQVEsRUFBRSxxQ0FBcUM7S0FDbEQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsdURBQXVEO0tBQ3BFLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFO1FBQ2IsUUFBUSxFQUFFLGFBQWE7S0FDMUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsd0RBQXdEO1FBQ2xFLE9BQU8sRUFBRSw4Q0FBOEM7S0FDMUQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsUUFBUSxFQUFFLDRFQUE0RTtRQUN0RixPQUFPLEVBQUUsRUFBRTtLQUNkLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFO1FBQ2YsUUFBUSxFQUFFLGlCQUFpQjtRQUMzQixPQUFPLEVBQUUseUJBQXlCO0tBQ3JDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLFFBQVEsRUFBRSw0RUFBNEU7UUFDdEYsT0FBTyxFQUFFLElBQUk7S0FDaEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7UUFDZCxRQUFRLEVBQUUsc0JBQXNCO1FBQ2hDLE9BQU8sRUFBRSxnQkFBZ0I7S0FDNUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFBO0FBRUQsSUFBSSxPQUFPLEdBQUcsQ0FBQyxLQUFlLEVBQUUsRUFBRSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQUd4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFhLEVBQUUsRUFBRTtJQUN0QyxPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsNkJBQTZCLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFtQixFQUFFLEVBQUU7UUFDeEcsUUFBUSxFQUFFLENBQUE7UUFFVixJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFDLE9BQU07UUFBQyxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxHQUFRLElBQUksQ0FBQTtRQUN0QixNQUFNLE1BQU0sR0FBUSxjQUFjLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2hELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUE7UUFDdEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O2NBbUNFO1FBQ04sQ0FBQztRQUNELElBQUksSUFBSSxLQUFLLE9BQU8sSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDL0IsTUFBTSxLQUFLLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBYSxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ3hFLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQSJ9
|
||||
7
packages/i18n/dist/lib/translate.js
vendored
7
packages/i18n/dist/lib/translate.js
vendored
File diff suppressed because one or more lines are too long
2
packages/i18n/dist/lib/translate_markdown.d.ts
vendored
Normal file
2
packages/i18n/dist/lib/translate_markdown.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
import { IOptions } from '../types.js';
|
||||
export declare const translateMarkdown: (src: string, dst: string, options: IOptions) => Promise<any>;
|
||||
57
packages/i18n/dist/lib/translate_markdown.js
vendored
Normal file
57
packages/i18n/dist/lib/translate_markdown.js
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import { OSR_CACHE } from '@polymech/commons';
|
||||
import { get_cached, set_cached } from '@polymech/cache';
|
||||
import { sync as read } from "@polymech/fs/read";
|
||||
import { sync as write } from "@polymech/fs/write";
|
||||
import { MODULE_NAME } from '../constants.js';
|
||||
import { translate } from '@polymech/deepl-mark';
|
||||
import { update as updateGlossary } from './glossary.js';
|
||||
import { logger } from './translate_commons.js';
|
||||
export const translateMarkdown = async (src, dst, options) => {
|
||||
const osr_cache = OSR_CACHE();
|
||||
const cached = await get_cached(src, { keys: options.keys, filters: options.filters }, MODULE_NAME);
|
||||
if (osr_cache && cached && options.cache) {
|
||||
return cached;
|
||||
}
|
||||
const srcContent = read(src);
|
||||
// Fetch glossary if enabled
|
||||
let glossaryId;
|
||||
if (options.glossary !== false) {
|
||||
try {
|
||||
const glossary = await updateGlossary((options.srcLang || 'EN').toLowerCase(), (options.dstLang || 'DE').toLowerCase(), options);
|
||||
if (glossary && glossary.glossaryId) {
|
||||
glossaryId = glossary.glossaryId;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.warn('Error updating glossary', e.message);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const apiKey = options.config?.deepl?.auth_key || options.api_key;
|
||||
if (!apiKey) {
|
||||
throw new Error('DeepL API key is missing.');
|
||||
}
|
||||
const translations = await translate(srcContent, (options.srcLang || 'EN').toUpperCase(), (options.dstLang || 'DE').toUpperCase(), {
|
||||
apiKey,
|
||||
deeplOptions: {
|
||||
formality: options.formality && options.formality !== 'default' ? options.formality : undefined,
|
||||
glossary: glossaryId
|
||||
}
|
||||
});
|
||||
if (osr_cache && options.cache) {
|
||||
await set_cached(src, { keys: options.keys, filters: options.filters }, MODULE_NAME, translations);
|
||||
}
|
||||
if (translations) {
|
||||
write(dst, translations);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return translations;
|
||||
}
|
||||
catch (e) {
|
||||
logger.error(`Error translating markdown file ${src}`, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhbnNsYXRlX21hcmtkb3duLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2xpYi90cmFuc2xhdGVfbWFya2Rvd24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQzdDLE9BQU8sRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLE1BQU0saUJBQWlCLENBQUE7QUFDeEQsT0FBTyxFQUFFLElBQUksSUFBSSxJQUFJLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNoRCxPQUFPLEVBQUUsSUFBSSxJQUFJLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBRWxELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQTtBQUM3QyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sc0JBQXNCLENBQUE7QUFDaEQsT0FBTyxFQUFFLE1BQU0sSUFBSSxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDeEQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFBO0FBRS9DLE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLEtBQUssRUFDbEMsR0FBVyxFQUNYLEdBQVcsRUFDWCxPQUFpQixFQUFFLEVBQUU7SUFFckIsTUFBTSxTQUFTLEdBQUcsU0FBUyxFQUFFLENBQUE7SUFDN0IsTUFBTSxNQUFNLEdBQUcsTUFBTSxVQUFVLENBQUMsR0FBRyxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRSxXQUFXLENBQUMsQ0FBQztJQUNwRyxJQUFJLFNBQVMsSUFBSSxNQUFNLElBQUksT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3ZDLE9BQU8sTUFBTSxDQUFBO0lBQ2pCLENBQUM7SUFFRCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFXLENBQUE7SUFFdEMsNEJBQTRCO0lBQzVCLElBQUksVUFBOEIsQ0FBQztJQUNuQyxJQUFJLE9BQU8sQ0FBQyxRQUFRLEtBQUssS0FBSyxFQUFFLENBQUM7UUFDN0IsSUFBSSxDQUFDO1lBQ0QsTUFBTSxRQUFRLEdBQUcsTUFBTSxjQUFjLENBQ2pDLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsQ0FBQyxXQUFXLEVBQUUsRUFDdkMsQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxDQUFDLFdBQVcsRUFBRSxFQUN2QyxPQUFPLENBQ1YsQ0FBQTtZQUNELElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQztnQkFDbEMsVUFBVSxHQUFHLFFBQVEsQ0FBQyxVQUFVLENBQUE7WUFDcEMsQ0FBQztRQUNMLENBQUM7UUFBQyxPQUFPLENBQU0sRUFBRSxDQUFDO1lBQ2QsTUFBTSxDQUFDLElBQUksQ0FBQyx5QkFBeUIsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDckQsQ0FBQztJQUNMLENBQUM7SUFFRCxJQUFJLENBQUM7UUFDRCxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxRQUFRLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQztRQUNsRSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDVixNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7UUFDakQsQ0FBQztRQUVELE1BQU0sWUFBWSxHQUFHLE1BQU0sU0FBUyxDQUFDLFVBQVUsRUFDM0MsQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxDQUFDLFdBQVcsRUFBUyxFQUM5QyxDQUFDLE9BQU8sQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFDLENBQUMsV0FBVyxFQUFTLEVBQzlDO1lBQ0ksTUFBTTtZQUNOLFlBQVksRUFBRTtnQkFDVixTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVMsSUFBSSxPQUFPLENBQUMsU0FBUyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFNBQWdCLENBQUMsQ0FBQyxDQUFDLFNBQVM7Z0JBQ3RHLFFBQVEsRUFBRSxVQUFVO2FBQ3ZCO1NBQ0osQ0FDSixDQUFBO1FBRUQsSUFBSSxTQUFTLElBQUksT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQzdCLE1BQU0sVUFBVSxDQUFDLEdBQUcsRUFBRSxFQUFFLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsV0FBVyxFQUFFLFlBQVksQ0FBQyxDQUFBO1FBQ3RHLENBQUM7UUFFRCxJQUFJLFlBQVksRUFBRSxDQUFDO1lBQ2YsS0FBSyxDQUFDLEdBQUcsRUFBRSxZQUFZLENBQUMsQ0FBQTtRQUM1QixDQUFDO2FBQU0sQ0FBQztZQUNKLE9BQU8sS0FBSyxDQUFBO1FBQ2hCLENBQUM7UUFDRCxPQUFPLFlBQVksQ0FBQTtJQUV2QixDQUFDO0lBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNULE1BQU0sQ0FBQyxLQUFLLENBQUMsbUNBQW1DLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFBO1FBQ3pELE9BQU8sS0FBSyxDQUFBO0lBQ2hCLENBQUM7QUFDTCxDQUFDLENBQUEifQ==
|
||||
3324
packages/i18n/package-lock.json
generated
3324
packages/i18n/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -101,7 +101,8 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/form-data": "^2.2.1",
|
||||
"nexe": "^5.0.0-beta.4",
|
||||
"vitest": "^4.1.2",
|
||||
"webpack-cli": "^7.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,20 @@
|
||||
{
|
||||
"translate-es": {
|
||||
"name": "Translate to Spanish - Internal",
|
||||
"name": "Translate to Spanish (es)",
|
||||
"command": "pm-i18n",
|
||||
"args": "translate --alt=true --src=\"$(FullName)\" --dst=\"&{SRC_DIR}/&{SRC_NAME}_&{DST_LANG}.md\" --srcLang='en' --dstLang='es'",
|
||||
"description": "Translate to EU"
|
||||
"args": "translate --src=\"$(FullName)\" --dstLang='es'",
|
||||
"description": "Translate file to Spanish (auto-detects source language)"
|
||||
},
|
||||
"translate-fr": {
|
||||
"name": "Translate to French (fr)",
|
||||
"command": "pm-i18n",
|
||||
"args": "translate --src=\"$(FullName)\" --dstLang='fr'",
|
||||
"description": "Translate file to French (auto-detects source language)"
|
||||
},
|
||||
"translate-en": {
|
||||
"name": "Translate to English (en)",
|
||||
"command": "pm-i18n",
|
||||
"args": "translate --src=\"$(FullName)\" --dstLang='en'",
|
||||
"description": "Translate file to English (auto-detects source language)"
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
// nexe.js - Compile pm-media to Windows executable using nexe Node.js API
|
||||
// nexe.js - Compile i18n to Windows executable using nexe Node.js API
|
||||
import { compile } from 'nexe';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
async function buildExecutable() {
|
||||
const outputDir = './dist/win-64';
|
||||
const outputFile = 'kbot.exe';
|
||||
const outputFile = 'pm-i18n.exe';
|
||||
const entryPoint = './dist-node/main_node.cjs';
|
||||
const nexeTemp = '../../nexe-24';
|
||||
const nodeVersion = '24.5.0';
|
||||
@ -40,34 +40,14 @@ async function buildExecutable() {
|
||||
await compile({
|
||||
input: entryPoint,
|
||||
output: outputPath,
|
||||
target: `windows-x64-${nodeVersion}`,
|
||||
build: true, // Build from source for native modules like sharp
|
||||
target: 'windows-x64-22.12.0',
|
||||
build: true,
|
||||
temp: nexeTemp,
|
||||
clean: false,
|
||||
name: 'pm-media',
|
||||
configure: ['--with-intl=full-icu'], // Full ICU support
|
||||
make: ['-j4'], // Parallel build
|
||||
name: 'pm-i18n',
|
||||
make: ['-j4'],
|
||||
loglevel: 'verbose',
|
||||
// Resources - include any additional files if needed
|
||||
resources: [
|
||||
// Add any resource patterns here if needed
|
||||
// './assets/**/*'
|
||||
],
|
||||
patches: [
|
||||
// Patch for better native module support
|
||||
async (compiler, next) => {
|
||||
// This patch helps with native modules like sharp
|
||||
await compiler.replaceInFileAsync(
|
||||
'lib/internal/bootstrap/pre_execution.js',
|
||||
'process.dlopen = function(',
|
||||
`
|
||||
// Nexe patch for native modules
|
||||
const originalDlopen = process.dlopen;
|
||||
process.dlopen = function(`
|
||||
);
|
||||
return next();
|
||||
}
|
||||
]
|
||||
resources: []
|
||||
});
|
||||
|
||||
console.log(`✅ Successfully compiled to ${outputPath}`);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
npm run webpack
|
||||
cp ./dist/main_node.js ./dist/main_node.cjs
|
||||
cp ./dist-node/main_node.js ./dist-node/main_node.cjs
|
||||
node scripts/nexe.js
|
||||
#mkdir -p dist/win-64/dist
|
||||
#cp dist/win-64/*.wasm dist/win-64/dist/
|
||||
|
||||
@ -44,14 +44,14 @@ let options = (yargs: CLI.Argv) => defaultOptions(yargs)
|
||||
|
||||
|
||||
export const register = (cli: CLI.Argv) => {
|
||||
return cli.command('glossary <verb>', 'Manages glossaries on DeepL', options, async (argv: CLI.Arguments) => {
|
||||
return cli.command('glossary <verb>', 'Manages glossaries on DeepL', options, async (argv: CLI.Arguments) => {
|
||||
defaults()
|
||||
|
||||
if (argv.help) { return }
|
||||
const args: any = argv
|
||||
const config: any = CONFIG_DEFAULT(args.env_key)
|
||||
const verb = argv.verb
|
||||
if (verb === 'create') {
|
||||
if (verb === 'create') {
|
||||
/*
|
||||
let options = sanitize(argv as any) as IOptions
|
||||
if (!options) {
|
||||
|
||||
@ -11,7 +11,8 @@ import { translateYAML } from './translate_yaml.js'
|
||||
import { translateXLS } from './translate_xls.js'
|
||||
import { translateText } from './translate_text.js'
|
||||
import { extension, getTranslation, logger, translateDeepL } from './translate_commons.js'
|
||||
import { translateDocument } from './translate_document.js'
|
||||
import { translateDocument } from './translate_document.js'
|
||||
import { translateMarkdown } from './translate_markdown.js'
|
||||
|
||||
export const translateFiles = async (
|
||||
file, targets: string[], options: IOptions) => {
|
||||
@ -98,7 +99,11 @@ export const getTranslator = (file: string, options?: IOptions) => {
|
||||
const hybridExtensions = ['.html', '.htm', '.xlsx', '.xls', '.txt'];
|
||||
|
||||
if (docExtensions.includes(ext) || (options?.engine === 'document' && hybridExtensions.includes(ext))) {
|
||||
return translateDocument;
|
||||
return translateDocument;
|
||||
}
|
||||
const markdownExtensions = ['.md', '.mdx'];
|
||||
if (options?.engine === 'markdown' && markdownExtensions.includes(ext)) {
|
||||
return translateMarkdown;
|
||||
}
|
||||
|
||||
return TRANSLATORS[ext] || null;
|
||||
|
||||
74
packages/i18n/src/lib/translate_markdown.ts
Normal file
74
packages/i18n/src/lib/translate_markdown.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { OSR_CACHE } from '@polymech/commons'
|
||||
import { get_cached, set_cached } from '@polymech/cache'
|
||||
import { sync as read } from "@polymech/fs/read"
|
||||
import { sync as write } from "@polymech/fs/write"
|
||||
import { IOptions } from '../types.js'
|
||||
import { MODULE_NAME } from '../constants.js'
|
||||
import { translate } from '@polymech/deepl-mark'
|
||||
import { update as updateGlossary } from './glossary.js'
|
||||
import { logger } from './translate_commons.js'
|
||||
|
||||
export const translateMarkdown = async (
|
||||
src: string,
|
||||
dst: string,
|
||||
options: IOptions) => {
|
||||
|
||||
const osr_cache = OSR_CACHE()
|
||||
const cached = await get_cached(src, { keys: options.keys, filters: options.filters }, MODULE_NAME);
|
||||
if (osr_cache && cached && options.cache) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const srcContent = read(src) as string
|
||||
|
||||
// Fetch glossary if enabled
|
||||
let glossaryId: string | undefined;
|
||||
if (options.glossary !== false) {
|
||||
try {
|
||||
const glossary = await updateGlossary(
|
||||
(options.srcLang || 'EN').toLowerCase(),
|
||||
(options.dstLang || 'DE').toLowerCase(),
|
||||
options
|
||||
)
|
||||
if (glossary && glossary.glossaryId) {
|
||||
glossaryId = glossary.glossaryId
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.warn('Error updating glossary', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKey = options.config?.deepl?.auth_key || options.api_key;
|
||||
if (!apiKey) {
|
||||
throw new Error('DeepL API key is missing.');
|
||||
}
|
||||
|
||||
const translations = await translate(srcContent,
|
||||
(options.srcLang || 'EN').toUpperCase() as any,
|
||||
(options.dstLang || 'DE').toUpperCase() as any,
|
||||
{
|
||||
apiKey,
|
||||
deeplOptions: {
|
||||
formality: options.formality && options.formality !== 'default' ? options.formality as any : undefined,
|
||||
glossary: glossaryId
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (osr_cache && options.cache) {
|
||||
await set_cached(src, { keys: options.keys, filters: options.filters }, MODULE_NAME, translations)
|
||||
}
|
||||
|
||||
if (translations) {
|
||||
write(dst, translations)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return translations
|
||||
|
||||
} catch (e) {
|
||||
logger.error(`Error translating markdown file ${src}`, e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -55,4 +55,34 @@ describe('DeepL Document API Translation (Live)', () => {
|
||||
expect(fs.existsSync(dstPath)).toBe(true);
|
||||
expect(fs.statSync(dstPath).size).toBeGreaterThan(0);
|
||||
}, 120000); // 120s timeout for real API call
|
||||
|
||||
it('should translate test.md specifically requesting engine=markdown', async () => {
|
||||
const srcPath = path.resolve(process.cwd(), 'tests/documents/test.md');
|
||||
const dstPath = path.resolve(process.cwd(), 'tests/documents/test_de.md');
|
||||
|
||||
// Assert the test file exists before running
|
||||
expect(fs.existsSync(srcPath)).toBe(true);
|
||||
|
||||
// Clean up any old output file
|
||||
if (fs.existsSync(dstPath)) {
|
||||
fs.unlinkSync(dstPath);
|
||||
}
|
||||
|
||||
const { stdout, stderr } = await exec(`node ${CLI_PATH} translate --src="${srcPath}" --srcLang="en" --dstLang="de" --engine="markdown"`);
|
||||
|
||||
console.log(stdout, stderr);
|
||||
// We verify the translation loop succeeds without errors.
|
||||
expect(stderr).not.toMatch(/error/i);
|
||||
|
||||
// Verify that the output document got generated correctly
|
||||
expect(fs.existsSync(dstPath)).toBe(true);
|
||||
|
||||
// Let's also ensure the file size is sane
|
||||
const size = fs.statSync(dstPath).size;
|
||||
expect(size).toBeGreaterThan(0);
|
||||
|
||||
// Check for encoding corruption (should not have UTF-16 BOM or gibberish)
|
||||
const translatedContent = fs.readFileSync(dstPath, 'utf-8');
|
||||
expect(translatedContent).not.toMatch(/�/);
|
||||
}, 120000); // 120s timeout for real API call
|
||||
})
|
||||
|
||||
119
packages/i18n/tests/documents/README.md
Normal file
119
packages/i18n/tests/documents/README.md
Normal file
@ -0,0 +1,119 @@
|
||||
# @polymech/deepl-mark
|
||||
|
||||
Translate markdown and MDX content using [DeepL](https://www.deepl.com/), powered by `mdast`.
|
||||
|
||||
Correctly handles headings, paragraphs, lists, tables (GFM), links, JSX components, frontmatter, and inline formatting — preserving structure while translating only the text.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @polymech/deepl-mark
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { translate } from '@polymech/deepl-mark';
|
||||
|
||||
const markdown = '# Hello World\n\nThis is a paragraph.';
|
||||
const result = await translate(markdown, 'en', 'de');
|
||||
|
||||
console.log(result);
|
||||
// # Hallo Welt
|
||||
//
|
||||
// Dies ist ein Absatz.
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Provide your DeepL API key via **options** or **environment variable**:
|
||||
|
||||
```ts
|
||||
// Option 1: pass directly
|
||||
await translate(md, 'en', 'de', { apiKey: 'your-deepl-key' });
|
||||
|
||||
// Option 2: environment variable
|
||||
// Set DEEPL_AUTH_KEY=your-deepl-key
|
||||
await translate(md, 'en', 'de');
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
The optional 4th argument accepts a `TranslateOptions` object:
|
||||
|
||||
```ts
|
||||
await translate(content, 'en', 'de', {
|
||||
// DeepL API key (falls back to DEEPL_AUTH_KEY env var)
|
||||
apiKey: '...',
|
||||
|
||||
// DeepL translation options (tagHandling, splitSentences, formality, glossaryId, etc.)
|
||||
deeplOptions: {
|
||||
formality: 'more',
|
||||
glossaryId: '...',
|
||||
},
|
||||
|
||||
// Frontmatter fields to include/exclude
|
||||
frontmatterFields: {
|
||||
include: ['title', 'description'],
|
||||
exclude: ['slug'],
|
||||
},
|
||||
|
||||
// Markdown node types to include/exclude (defaults: exclude 'code')
|
||||
markdownNodes: {
|
||||
exclude: ['code'],
|
||||
},
|
||||
|
||||
// HTML elements to include/exclude
|
||||
htmlElements: {
|
||||
exclude: ['pre', 'code'],
|
||||
},
|
||||
|
||||
// JSX components to include/exclude (with attribute-level control)
|
||||
jsxComponents: {
|
||||
include: {
|
||||
Card: { children: true, attributes: ['header'] },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### DeepL defaults
|
||||
|
||||
The following DeepL options are applied by default and can be overridden via `deeplOptions`:
|
||||
|
||||
| Option | Default |
|
||||
|------------------|----------------|
|
||||
| `tagHandling` | `'html'` |
|
||||
| `splitSentences` | `'nonewlines'` |
|
||||
|
||||
### Supported content
|
||||
|
||||
- **Markdown** (`.md`) — headings, paragraphs, lists, blockquotes, tables (GFM), links, images
|
||||
- **MDX** (`.mdx`) — JSX components and expressions
|
||||
- **Frontmatter** — YAML frontmatter fields
|
||||
- **HTML** — inline HTML elements and attributes
|
||||
|
||||
## API
|
||||
|
||||
### `translate(content, sourceLang, targetLang, options?)`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|--------------|------------------------|-----------------------------------------------|
|
||||
| `content` | `string` | Markdown or MDX string to translate |
|
||||
| `sourceLang` | `SourceLanguageCode` | Source language (e.g. `'en'`, `'de'`, `'fr'`) |
|
||||
| `targetLang` | `TargetLanguageCode` | Target language (e.g. `'de'`, `'en-US'`) |
|
||||
| `options` | `TranslateOptions` | Optional config (see above) |
|
||||
|
||||
Returns `Promise<string>` — the translated markdown.
|
||||
|
||||
## Scripts
|
||||
|
||||
```bash
|
||||
npm test # run all tests
|
||||
npm run test:tables # run table translation e2e test
|
||||
npm run build # build for distribution
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
27
packages/i18n/tests/documents/test.md
Normal file
27
packages/i18n/tests/documents/test.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Markdown Translation E2E Test"
|
||||
description: "Ensure that frontmatter and complex formatting works 🚀"
|
||||
tags: ["i18n", "documents"]
|
||||
---
|
||||
|
||||
# DeepL Markdown Test 🌍
|
||||
|
||||
Welcome to the automated DeepL testing suite!
|
||||
|
||||
## Features
|
||||
|
||||
- **Formatting preservation**: `code snippets`, *italics*, and **bold**.
|
||||
- **Emojis**: 🤖 ✨ 🔥
|
||||
- **Links**: Check out [PolyMech](https://example.com/polymech).
|
||||
|
||||
> "This blockquote must survive."
|
||||
|
||||
1. First item with a [link](#foo)
|
||||
2. Second item
|
||||
|
||||
```json
|
||||
{
|
||||
"test": true,
|
||||
"config": "valid"
|
||||
}
|
||||
```
|
||||
27
packages/i18n/tests/documents/test_de.md
Normal file
27
packages/i18n/tests/documents/test_de.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Markdown Translation E2E Test"
|
||||
description: "Ensure that frontmatter and complex formatting works 🚀"
|
||||
tags: ["i18n", "documents"]
|
||||
---
|
||||
|
||||
# DeepL Markdown Test 🌍
|
||||
|
||||
Willkommen bei der automatisierten Test-Suite DeepL!
|
||||
|
||||
## Eigenschaften
|
||||
|
||||
* **Erhaltung der Formatierung**: `code snippets`, *Kursivschrift*und **fett**.
|
||||
* **Emojis**: 🤖 ✨ 🔥
|
||||
* **Links**: Auschecken [PolyMech](https://example.com/polymech).
|
||||
|
||||
> "Dieses Blockquote muss überleben."
|
||||
|
||||
1. Erster Artikel mit einer [Link](#foo)
|
||||
2. Zweiter Punkt
|
||||
|
||||
```json
|
||||
{
|
||||
"test": true,
|
||||
"config": "valid"
|
||||
}
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user