diff --git a/packages/i18n/dist/index.d.ts b/packages/i18n/dist/index.d.ts index c4234e13..ca7ed5f9 100644 --- a/packages/i18n/dist/index.d.ts +++ b/packages/i18n/dist/index.d.ts @@ -7,3 +7,5 @@ export * from './zod_schema.js'; export * from './zod_types.js'; export * from './constants.js'; export declare const logger: any; +export * from './lib/translate_commons.js'; +export * from './lib/translate_text.js'; diff --git a/packages/i18n/dist/index.js b/packages/i18n/dist/index.js index 97872a65..458c16c9 100644 --- a/packages/i18n/dist/index.js +++ b/packages/i18n/dist/index.js @@ -8,4 +8,6 @@ export * from './zod_schema.js'; export * from './zod_types.js'; export * from './constants.js'; export const logger = createLogger('commons'); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLE9BQU8sQ0FBQTtBQUM5QixPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBRTVDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQUM1QyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBRXBDLGNBQWMsY0FBYyxDQUFBO0FBQzVCLGNBQWMsWUFBWSxDQUFBO0FBQzFCLGNBQWMsaUJBQWlCLENBQUE7QUFDL0IsY0FBYyxnQkFBZ0IsQ0FBQTtBQUM5QixjQUFjLGdCQUFnQixDQUFBO0FBRTlCLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBTyxZQUFZLENBQUMsU0FBUyxDQUFDLENBQUEifQ== \ No newline at end of file +export * from './lib/translate_commons.js'; +export * from './lib/translate_text.js'; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLE9BQU8sQ0FBQTtBQUM5QixPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBRTVDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQUM1QyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBRXBDLGNBQWMsY0FBYyxDQUFBO0FBQzVCLGNBQWMsWUFBWSxDQUFBO0FBQzFCLGNBQWMsaUJBQWlCLENBQUE7QUFDL0IsY0FBYyxnQkFBZ0IsQ0FBQTtBQUM5QixjQUFjLGdCQUFnQixDQUFBO0FBRTlCLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBUSxZQUFZLENBQUMsU0FBUyxDQUFDLENBQUE7QUFFbEQsY0FBYyw0QkFBNEIsQ0FBQTtBQUMxQyxjQUFjLHlCQUF5QixDQUFBIn0= \ No newline at end of file diff --git a/packages/i18n/dist/lib/deepl.d.ts b/packages/i18n/dist/lib/deepl.d.ts index 527f8b65..e7d3eda2 100644 --- a/packages/i18n/dist/lib/deepl.d.ts +++ b/packages/i18n/dist/lib/deepl.d.ts @@ -24,6 +24,9 @@ export interface IDeepLOptions { splitting_tags?: string[]; ignore_tags?: string[]; glossary_id?: string; + name?: string; + entries?: string; + entries_format?: string; } export interface IDeepLResponse { translations: { diff --git a/packages/i18n/dist/lib/deepl.js b/packages/i18n/dist/lib/deepl.js index 318afa16..901a328b 100644 --- a/packages/i18n/dist/lib/deepl.js +++ b/packages/i18n/dist/lib/deepl.js @@ -14,14 +14,8 @@ export const translate_deepl = async (parameters) => { const sub_domain = parameters.free_api ? 'api-free' : 'api'; try { let params = stringify(parameters); + // console.log('DEBUG: translate_deepl params:', params); return axios.post(`https://${sub_domain}.deepl.com/v2/translate`, params, {}); - // return (ret as any).data - /* - const ret = await axios.post( - `https://${sub_domain}.deepl.com/v2/translate`, - params, {}) as any - return (ret as any).data - */ } catch (error) { console.error('error : translate_deepl', error.message); @@ -31,14 +25,28 @@ export const translate_deepl = async (parameters) => { export const create_glossary = async (parameters) => { const sub_domain = parameters.free_api ? 'api-free' : 'api'; try { - const params = stringify(parameters); - "auth_key=34dbf59f-adeb-1959-d906-502e0ec6afb8&free_api=false&name=pp&target_lang=DE&source_lang=EN&entries=Mould%2CKunststoffform%0D%0AShredder%2CGranulator%0D%0A&entries_format=csv"; - const ret = await axios.post(`https://${sub_domain}.deepl.com/v2/glossaries`, params); + // v3 requires structured payload + const p = parameters; + const payload = { + name: p.name || 'Glossary', + dictionaries: [{ + source_lang: p.source_lang, + target_lang: p.target_lang, + entries: p.entries, + entries_format: p.entries_format || 'tsv' + }] + }; + const ret = await axios.post(`https://${sub_domain}.deepl.com/v3/glossaries`, payload, { + headers: { + 'Authorization': `DeepL-Auth-Key ${parameters.auth_key}`, + 'Content-Type': 'application/json' + } + }); return ret.data; } catch (error) { //@todo : never called - console.error(error.response.data); + console.error(error.response?.data || error.message); } }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL2RlZXBsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUE7QUFDdkMsT0FBTyxFQUFFLE9BQU8sSUFBSSxLQUFLLEVBQUUsTUFBTSxPQUFPLENBQUE7QUF5Q3hDLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLEtBQUssRUFDakMsVUFBeUIsRUFDM0IsRUFBRTtJQUNBLElBQUksQ0FBQztRQUNELE1BQU0sTUFBTSxHQUFHLGtLQUFrSyxDQUFBO1FBQ2pMLE1BQU0sR0FBRyxHQUFJLEtBQUssQ0FDZCxvQ0FBb0MsRUFDcEMsTUFBYSxDQUFRLENBQUE7UUFDekIsT0FBTyxHQUFHLENBQUE7SUFDZCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNiLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQ2hDLENBQUM7QUFDTCxDQUFDLENBQUE7QUFDRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsS0FBSyxFQUNoQyxVQUF5QixFQUMzQixFQUFFO0lBQ0EsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7SUFDNUQsSUFBSSxDQUFDO1FBQ0QsSUFBSSxNQUFNLEdBQUcsU0FBUyxDQUFDLFVBQWlCLENBQUMsQ0FBQTtRQUN6QyxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQ2IsV0FBVyxVQUFVLHlCQUF5QixFQUM5QyxNQUFNLEVBQUUsRUFBRSxDQUFRLENBQUE7UUFDdEIsMkJBQTJCO1FBQ25DOzs7OztrQkFLVTtJQUNOLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2IsT0FBTyxDQUFDLEtBQUssQ0FBQyx5QkFBeUIsRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDdkQsOEJBQThCO0lBQ2xDLENBQUM7QUFDTCxDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsS0FBSyxFQUNoQyxVQUF5QixFQUMzQixFQUFFO0lBQ0EsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUE7SUFDM0QsSUFBSSxDQUFDO1FBQ0QsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLFVBQWlCLENBQUMsQ0FBQTtRQUMzQyx1TEFBdUwsQ0FBQTtRQUN2TCxNQUFNLEdBQUcsR0FBRyxNQUFPLEtBQUssQ0FBQyxJQUFJLENBQ3pCLFdBQVcsVUFBVSwwQkFBMEIsRUFDL0MsTUFBTSxDQUFTLENBQUE7UUFFbkIsT0FBUSxHQUFXLENBQUMsSUFBSSxDQUFBO0lBRTVCLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2Isc0JBQXNCO1FBQ3RCLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUN0QyxDQUFDO0FBQ0wsQ0FBQyxDQUFBIn0= \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL2RlZXBsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUE7QUFDdkMsT0FBTyxFQUFFLE9BQU8sSUFBSSxLQUFLLEVBQUUsTUFBTSxPQUFPLENBQUE7QUE2Q3hDLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLEtBQUssRUFDakMsVUFBeUIsRUFDM0IsRUFBRTtJQUNBLElBQUksQ0FBQztRQUNELE1BQU0sTUFBTSxHQUFHLGtLQUFrSyxDQUFBO1FBQ2pMLE1BQU0sR0FBRyxHQUFHLEtBQUssQ0FDYixvQ0FBb0MsRUFDcEMsTUFBYSxDQUFRLENBQUE7UUFDekIsT0FBTyxHQUFHLENBQUE7SUFDZCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNiLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQ2hDLENBQUM7QUFDTCxDQUFDLENBQUE7QUFDRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsS0FBSyxFQUNoQyxVQUF5QixFQUMzQixFQUFFO0lBQ0EsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7SUFDNUQsSUFBSSxDQUFDO1FBQ0QsSUFBSSxNQUFNLEdBQUcsU0FBUyxDQUFDLFVBQWlCLENBQUMsQ0FBQTtRQUN6Qyx5REFBeUQ7UUFDekQsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUNiLFdBQVcsVUFBVSx5QkFBeUIsRUFDOUMsTUFBTSxFQUFFLEVBQUUsQ0FBUSxDQUFBO0lBQzFCLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2IsT0FBTyxDQUFDLEtBQUssQ0FBQyx5QkFBeUIsRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDdkQsOEJBQThCO0lBQ2xDLENBQUM7QUFDTCxDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsS0FBSyxFQUNoQyxVQUF5QixFQUMzQixFQUFFO0lBQ0EsTUFBTSxVQUFVLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUE7SUFDM0QsSUFBSSxDQUFDO1FBQ0QsaUNBQWlDO1FBQ2pDLE1BQU0sQ0FBQyxHQUFRLFVBQVUsQ0FBQztRQUMxQixNQUFNLE9BQU8sR0FBRztZQUNaLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxJQUFJLFVBQVU7WUFDMUIsWUFBWSxFQUFFLENBQUM7b0JBQ1gsV0FBVyxFQUFFLENBQUMsQ0FBQyxXQUFXO29CQUMxQixXQUFXLEVBQUUsQ0FBQyxDQUFDLFdBQVc7b0JBQzFCLE9BQU8sRUFBRSxDQUFDLENBQUMsT0FBTztvQkFDbEIsY0FBYyxFQUFFLENBQUMsQ0FBQyxjQUFjLElBQUksS0FBSztpQkFDNUMsQ0FBQztTQUNMLENBQUM7UUFFRixNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQ3hCLFdBQVcsVUFBVSwwQkFBMEIsRUFDL0MsT0FBTyxFQUNQO1lBQ0ksT0FBTyxFQUFFO2dCQUNMLGVBQWUsRUFBRSxrQkFBa0IsVUFBVSxDQUFDLFFBQVEsRUFBRTtnQkFDeEQsY0FBYyxFQUFFLGtCQUFrQjthQUNyQztTQUNKLENBQ0csQ0FBQTtRQUVSLE9BQVEsR0FBVyxDQUFDLElBQUksQ0FBQTtJQUU1QixDQUFDO0lBQUMsT0FBTyxLQUFVLEVBQUUsQ0FBQztRQUNsQixzQkFBc0I7UUFDdEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLElBQUksSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDeEQsQ0FBQztBQUNMLENBQUMsQ0FBQSJ9 \ No newline at end of file diff --git a/packages/i18n/docs/overview.md b/packages/i18n/docs/overview.md new file mode 100644 index 00000000..17b65282 --- /dev/null +++ b/packages/i18n/docs/overview.md @@ -0,0 +1,75 @@ +# Translation Library Overview (`translate.ts`) + +## Description +The `translate.ts` module serves as the core orchestration layer for the i18n package. It handles: +1. Parsing and validating configuration options. +2. Determining the translation mode (text vs. file). +3. Resolving file paths and glob patterns. +4. Dispatching files to appropriate translators based on file extension. +5. Managing API interactions via the DeepL integration. + +## Key Exports + +### `translate(opts: IOptions)` +The main entry point for the translation process. +- **Parameters**: `opts` - Configuration object containing source paths, destination languages, API keys, etc. +- **Logic Flow**: + 1. Validates required options (`api_key`, `src`/`text`, `dstLang`). + 2. Resolves root paths and environmental variables. + 3. Iterates through target languages (`dstLang`). + 4. If `opts.text` is present: Calls `translateDeepL` directly for string translation. + 5. If `opts.src` is present: Uses `p-map` to process files concurrently. + +### `translateFiles(file, targets, options)` +Handles the translation of a specific file for a set of target languages. +- **Parameters**: + - `file`: Source file path. + - `targets`: Array of target file paths/languages. + - `options`: Global configuration options. +- **Logic Flow**: + 1. Identifies the correct translator using `getTranslator(file)`. + 2. Checks for dry-run mode (`options.dry`). + 3. Executes the translation for each target concurrently. + +### `getTranslator(file: string)` +Utility function that returns the appropriate translator function based on the file extension. + +## Supported File Types +The module uses a `TRANSLATORS` map to route files to specific handlers: + +| Extension | Handler | Description | +| :--- | :--- | :--- | +| `.md`, `.html` | `translateMarkup` | Handles Markdown and HTML content preservation. | +| `.json` | `translateJSON` | Translates JSON values while preserving keys. | +| `.toml` | `translateTOML` | Translates TOML configuration files. | +| `.yaml` | `translateYAML` | Translates YAML configuration files. | +| `.xlsx`, `.xls`| `translateXLS` | Handles Excel spreadsheet translation. | +| `.txt` | `translateText` | Simple text file translation. | + +## Dependencies +- **`p-map`**: Used to manage concurrency for file processing and language iteration (limited to concurrency: 1 by default in `translateFiles`). +- **`path`**: For filesystem path resolution. +- **`@polymech/commons`**: specifically `globBase` for handling glob patterns in source paths. + +## Glossary System + +The package supports term consistency through a glossary system integrated with DeepL's glossary feature. + +### 1. Storage Structure +Glossaries are stored locally in the configured `storeRoot` (default: `${OSR_ROOT}/i18n-store`): +- **CSV Data**: `/glossary///glossary.csv` contains the term pairs. +- **Metadata**: `/glossary///glossary.json` tracks the `glossaryId`, creation time, and content hash. + +### 2. Synchronization Logic +To ensure the local glossary matches the one used by DeepL, the system checks for consistency before every translation request: + +1. **Hash Verification**: It calculates an MD5 hash of the local CSV file and compares it with the hash stored in the metadata file. +2. **Auto-Update**: + - If the hashes differ (indicating local changes) or if the glossary is missing on the remote, it triggers an update. + - **Re-creation**: The old glossary is deleted from DeepL, and a new one is created using the current CSV content. + - **Metadata Update**: The new `glossaryId` and hash are saved locally. + +### 3. Usage +When `translateDeepL` is called, it automatically: +- Attempts to resolve/update the glossary for the requested language pair. +- Injects the `glossary_id` into the DeepL API request options if a valid glossary exists. diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index 9c2ff398..c94c8792 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -10,5 +10,9 @@ export * from './zod_schema.js' export * from './zod_types.js' export * from './constants.js' -export const logger:any = createLogger('commons') +export const logger: any = createLogger('commons') + +export * from './lib/translate_commons.js' +export * from './lib/translate_text.js' + diff --git a/packages/i18n/src/lib/deepl.ts b/packages/i18n/src/lib/deepl.ts index 3522b747..6ea1c41e 100644 --- a/packages/i18n/src/lib/deepl.ts +++ b/packages/i18n/src/lib/deepl.ts @@ -29,6 +29,10 @@ export interface IDeepLOptions { splitting_tags?: string[]; ignore_tags?: string[]; glossary_id?: string; + // v3 glossary params (optional in interface but used in create_glossary) + name?: string; + entries?: string; + entries_format?: string; } @@ -45,9 +49,9 @@ export const translate_deeplT = async ( ) => { try { const params = "preserve_formatting=1&tag_handling=xml&auth_key=4f6441ab-8e09-48d3-9031-3ca5cd463f79&formality=default&free_api=false&text=Variant&target_lang=de&source_lang=en" - const ret = axios ( + const ret = axios( `https://api.deepl.com/v2/translate`, - params as any) as any + params as any) as any return ret } catch (error) { console.error(error.message) @@ -59,16 +63,10 @@ export const translate_deepl = async ( const sub_domain = parameters.free_api ? 'api-free' : 'api'; try { let params = stringify(parameters as any) + // console.log('DEBUG: translate_deepl params:', params); return axios.post( `https://${sub_domain}.deepl.com/v2/translate`, params, {}) as any - // return (ret as any).data -/* - const ret = await axios.post( - `https://${sub_domain}.deepl.com/v2/translate`, - params, {}) as any - return (ret as any).data - */ } catch (error) { console.error('error : translate_deepl', error.message) //console.error(error.message) @@ -80,17 +78,34 @@ export const create_glossary = async ( ) => { const sub_domain = parameters.free_api ? 'api-free' : 'api' try { - const params = stringify(parameters as any) - "auth_key=34dbf59f-adeb-1959-d906-502e0ec6afb8&free_api=false&name=pp&target_lang=DE&source_lang=EN&entries=Mould%2CKunststoffform%0D%0AShredder%2CGranulator%0D%0A&entries_format=csv" - const ret = await (axios.post( - `https://${sub_domain}.deepl.com/v2/glossaries`, - params) as any) + // v3 requires structured payload + const p: any = parameters; + const payload = { + name: p.name || 'Glossary', + dictionaries: [{ + source_lang: p.source_lang, + target_lang: p.target_lang, + entries: p.entries, + entries_format: p.entries_format || 'tsv' + }] + }; + + const ret = await axios.post( + `https://${sub_domain}.deepl.com/v3/glossaries`, + payload, + { + headers: { + 'Authorization': `DeepL-Auth-Key ${parameters.auth_key}`, + 'Content-Type': 'application/json' + } + } + ) as any return (ret as any).data - } catch (error) { + } catch (error: any) { //@todo : never called - console.error(error.response.data) + console.error(error.response?.data || error.message) } } @@ -123,4 +138,3 @@ export type DeepLLanguages = | 'SL' | 'SV' | 'ZH' - diff --git a/packages/i18n/tests/EN/de/glossary-test.md b/packages/i18n/tests/EN/de/glossary-test.md index 46e46158..a19a0da5 100644 --- a/packages/i18n/tests/EN/de/glossary-test.md +++ b/packages/i18n/tests/EN/de/glossary-test.md @@ -1,7 +1,7 @@ -Das ist eine Plattenpresse und eine Spritzgießmaschine (Sie brauchen auch einen Granulator), und wenn Sie nett zu uns sind, geben wir Ihnen noch ein paar Spritzgussformen obendrauf, kostenlos und Open Source - weil wir Sie so sehr lieben! Sehen Sie sich mehr unserer Plattenpressen an. +Das ist eine Plattenpresse und eine Spritzgießmaschine (Sie brauchen auch einen Granulator), und wenn Sie nett zu uns sind, legen wir noch ein paar Spritzgussformen obendrauf, kostenlos und Open Source - weil wir Sie so sehr lieben! Sehen Sie sich mehr unserer Plattenpressen an. Die Abmessungen des Pakets sind ein Meter mal ein Meter. -Nachstehend finden Sie die aktuellen Optionen für Plattenpressen, Extruder und Injektoren. Sie können uns während der Bürozeiten unter [[TELEFON]] oder per E-Mail unter [[EMAIL]] erreichen +Nachstehend finden Sie die aktuellen Optionen für Plattenpressen, Extruder und Spritzgießmaschinen. Sie können uns während der Bürozeiten unter [[TELEFON]] oder per E-Mail unter [[EMAIL]] erreichen Cassandra 45 cm