document | deepl-markdown engine & tests

This commit is contained in:
lovebird 2026-04-05 14:51:36 +02:00
parent ef493ae64a
commit 431203c542
20 changed files with 411223 additions and 249 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -89,4 +89,4 @@ export const register = (cli) => {
}
});
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2xvc3NhcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tbWFuZHMvZ2xvc3NhcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFFNUIsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQzFDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDckMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2xELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUVyRCxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQ3ZDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDekIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsZ0JBQWdCO1FBQzFCLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLE9BQU8sRUFBRSxZQUFZO1FBQ3JCLFFBQVEsRUFBRSxxQ0FBcUM7S0FDbEQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsdURBQXVEO0tBQ3BFLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFO1FBQ2IsUUFBUSxFQUFFLGFBQWE7S0FDMUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsd0RBQXdEO1FBQ2xFLE9BQU8sRUFBRSw4Q0FBOEM7S0FDMUQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsUUFBUSxFQUFFLDRFQUE0RTtRQUN0RixPQUFPLEVBQUUsRUFBRTtLQUNkLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFO1FBQ2YsUUFBUSxFQUFFLGlCQUFpQjtRQUMzQixPQUFPLEVBQUUseUJBQXlCO0tBQ3JDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLFFBQVEsRUFBRSw0RUFBNEU7UUFDdEYsT0FBTyxFQUFFLElBQUk7S0FDaEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7UUFDZCxRQUFRLEVBQUUsc0JBQXNCO1FBQ2hDLE9BQU8sRUFBRSxnQkFBZ0I7S0FDNUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFBO0FBRUQsSUFBSSxPQUFPLEdBQUcsQ0FBQyxLQUFlLEVBQUUsRUFBRSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQUd4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFhLEVBQUUsRUFBRTtJQUN0QyxPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsNkJBQTZCLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFtQixFQUFHLEVBQUU7UUFDekcsUUFBUSxFQUFFLENBQUE7UUFFVixJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFDLE9BQU07UUFBQyxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxHQUFRLElBQUksQ0FBQTtRQUN0QixNQUFNLE1BQU0sR0FBUSxjQUFjLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2hELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUE7UUFDdEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O2NBbUNFO1FBQ04sQ0FBQztRQUNELElBQUksSUFBSSxLQUFLLE9BQU8sSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDL0IsTUFBTSxLQUFLLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBYSxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ3hFLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQSJ9
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2xvc3NhcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tbWFuZHMvZ2xvc3NhcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFFNUIsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQzFDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDckMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2xELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUVyRCxNQUFNLGNBQWMsR0FBRyxDQUFDLEtBQWUsRUFBRSxFQUFFO0lBQ3ZDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDekIsT0FBTyxFQUFFLEtBQUs7UUFDZCxRQUFRLEVBQUUsZ0JBQWdCO1FBQzFCLElBQUksRUFBRSxTQUFTO0tBQ2xCLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLE9BQU8sRUFBRSxZQUFZO1FBQ3JCLFFBQVEsRUFBRSxxQ0FBcUM7S0FDbEQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsdURBQXVEO0tBQ3BFLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFO1FBQ2IsUUFBUSxFQUFFLGFBQWE7S0FDMUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixRQUFRLEVBQUUsd0RBQXdEO1FBQ2xFLE9BQU8sRUFBRSw4Q0FBOEM7S0FDMUQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDakIsUUFBUSxFQUFFLDRFQUE0RTtRQUN0RixPQUFPLEVBQUUsRUFBRTtLQUNkLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFO1FBQ2YsUUFBUSxFQUFFLGlCQUFpQjtRQUMzQixPQUFPLEVBQUUseUJBQXlCO0tBQ3JDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFO1FBQ2pCLFFBQVEsRUFBRSw0RUFBNEU7UUFDdEYsT0FBTyxFQUFFLElBQUk7S0FDaEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7UUFDZCxRQUFRLEVBQUUsc0JBQXNCO1FBQ2hDLE9BQU8sRUFBRSxnQkFBZ0I7S0FDNUIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUU7UUFDYixPQUFPLEVBQUUsS0FBSztRQUNkLFFBQVEsRUFBRSx5Q0FBeUM7UUFDbkQsSUFBSSxFQUFFLFNBQVM7S0FDbEIsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFBO0FBRUQsSUFBSSxPQUFPLEdBQUcsQ0FBQyxLQUFlLEVBQUUsRUFBRSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQUd4RCxNQUFNLENBQUMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFhLEVBQUUsRUFBRTtJQUN0QyxPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsNkJBQTZCLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFtQixFQUFFLEVBQUU7UUFDeEcsUUFBUSxFQUFFLENBQUE7UUFFVixJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFDLE9BQU07UUFBQyxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxHQUFRLElBQUksQ0FBQTtRQUN0QixNQUFNLE1BQU0sR0FBUSxjQUFjLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2hELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUE7UUFDdEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDcEI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O2NBbUNFO1FBQ04sQ0FBQztRQUNELElBQUksSUFBSSxLQUFLLE9BQU8sSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDL0IsTUFBTSxLQUFLLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBYSxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ3hFLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQSJ9

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { IOptions } from '../types.js';
export declare const translateMarkdown: (src: string, dst: string, options: IOptions) => Promise<any>;

View 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==

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}
}

View File

@ -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)"
}
}

View File

@ -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}`);

View File

@ -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/

View File

@ -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) {

View File

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

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

View File

@ -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
})

View 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

View 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"
}
```

View 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"
}
```