diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..5b882b3 --- /dev/null +++ b/example/README.md @@ -0,0 +1,52 @@ +# MTranServer Usage Examples + +This directory contains examples of how to use `mtranserver` programmatically in your Node.js/Bun applications. + +## Prerequisites + +Ensure you have installed the library: + +```bash +bun install mtranserver@latest +``` + +## Running the Example + +You can run the example using `bun`: + +```bash +bun example/usage.ts +``` + +## Code Explanation + +The key component is the `MTran` class: + +```typescript +import { MTran } from 'mtranserver'; + +// 1. Create instance +const mtran = new MTran({ + modelDir: './models' // Directory to store translation models +}); + +// 2. Initialize +await mtran.init(); + +// 3. Translate +const result = await mtran.translate('en', 'es', 'Hello world'); +console.log(result); // "Hola mundo" + +// 4. Cleanup +await mtran.close(); +``` + +## Configuration + +The `MTran` constructor accepts a config object with the following optional properties: + +- `modelDir`: Path to store downloaded models. +- `configDir`: Path to store configuration files (like `records.json`). +- `logLevel`: Logging verbosity ('debug', 'info', 'warn', 'error'). +- `workersPerLanguage`: Number of parallel workers for translation. +- `enableOfflineMode`: If true, disables auto-downloading of models. diff --git a/example/usage.ts b/example/usage.ts new file mode 100644 index 0000000..a3cb988 --- /dev/null +++ b/example/usage.ts @@ -0,0 +1,52 @@ +import { MTran } from 'mtranserver'; + +async function main() { + console.log('--- MTranServer Library Usage Example ---'); + + // 1. Initialize MTran instance + // You can pass configuration options here. + const mtran = new MTran(); + + try { + // 2. Initialize (loads records, prepares environment) + console.log('Initializing MTran...'); + await mtran.init(); + + const fromLang = 'en'; + const toLang = 'zh'; + const text = 'Hello world! This is a library test.'; + + // 3. (Optional) Ensure the model is downloaded. + // .translate() will error if the model is missing in offline mode, + // or attempt to download it if online (default). + // Calling downloadModel explicitly is good for pre-warming. + console.log(`Checking model for ${fromLang} -> ${toLang}...`); + await mtran.downloadModel(fromLang, toLang); + + // 4. Perform Translation + console.log(` +Translating: "${text}"`); + const start = performance.now(); + + const result = await mtran.translate(fromLang, toLang, text); + + const end = performance.now(); + console.log(`Translation Result: "${result}"`); + console.log(`Time taken: ${(end - start).toFixed(2)}ms`); + + // 5. Language Detection + console.log('\nDetecting language for: "Bonjour tout le monde"'); + const detected = await mtran.detect("Bonjour tout le monde"); + console.log(`Detected: ${detected}`); + + } catch (error) { + console.error('An error occurred:', error); + } finally { + // 6. Cleanup + // Important: Stops background worker threads to allow process to exit cleanly + console.log('\nCleaning up...'); + await mtran.close(); + } +} + +main(); \ No newline at end of file diff --git a/package.json b/package.json index bbfdce4..3d137a9 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "version": "4.0.13", "type": "module", "description": "Translation server", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "bin": { "mtranserver": "./dist/main.js" }, "files": [ - "mtranserver", + "dist", "assets" ], "scripts": { @@ -17,6 +19,8 @@ "build:docker": "bun scripts/build.ts --docker", "build:node": "bun run gen && rm -rf dist && bun build src/main.ts --outdir dist --target node --format esm --sourcemap --external zstd-wasm-decoder --external express", "build:dev": "bun run gen && rm -rf dist && bun build src/main.ts --outdir dist --target node --format esm --sourcemap --external zstd-wasm-decoder --external express", + "build:lib": "bun run gen && rm -rf dist && rm -f ui/dist/assets/*.d.ts && bun build src/index.ts src/main.ts --outdir dist --target node --format esm --sourcemap --external zstd-wasm-decoder --external express && tsc -p tsconfig.lib.json", + "prepublishOnly": "bun run build:lib", "start": "node dist/main.js", "test": "bun test", "typecheck": "tsc --noEmit", diff --git a/src/config/index.ts b/src/config/index.ts index 0e81ec4..e89b5fb 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -134,3 +134,8 @@ export function getConfig(): Config { return globalConfig; } + +export function setConfig(config: Partial) { + const current = getConfig(); + globalConfig = { ...current, ...config }; +} diff --git a/src/globals.d.ts b/src/globals.d.ts index 4b9581d..2a3a96c 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -2,3 +2,23 @@ declare module '*.wasm' { const path: string; export default path; } + +declare module '*.css' { + const path: string; + export default path; +} + +declare module '*.js' { + const path: string; + export default path; +} + +declare module '*.html' { + const path: string; + export default path; +} + +declare module '*.png' { + const path: string; + export default path; +} diff --git a/src/index.ts b/src/index.ts index 0bdd98a..e69de29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +0,0 @@ -export * from '@/config/index.js'; -export * from '@/logger/index.js'; -export * from '@/utils/index.js'; -export * from '@/version/index.js'; -export * from '@/models/index.js'; -export * from '@/services/index.js'; -export * from '@/middleware/index.js'; -export * from '@/server/index.js'; diff --git a/src/mtran.ts b/src/mtran.ts new file mode 100644 index 0000000..7383f8d --- /dev/null +++ b/src/mtran.ts @@ -0,0 +1,68 @@ +import { setConfig, getConfig, Config } from '@/config/index.js'; +import { initRecords, downloadModel } from '@/models/index.js'; +import { translateWithPivot, cleanupAllEngines } from '@/services/index.js'; +import { detectLanguage } from '@/services/detector.js'; +import { normalizeLanguageCode } from '@/utils/index.js'; + +export interface MTranConfig extends Partial {} + +export class MTran { + constructor(config?: MTranConfig) { + if (config) { + setConfig(config); + } + } + + /** + * Initialize the translation engine (load records, etc.) + */ + async init(): Promise { + await initRecords(); + } + + /** + * Translate text + * @param from Source language code (or 'auto') + * @param to Target language code + * @param text Text to translate + * @param html Whether the text is HTML (default: false) + */ + async translate(from: string, to: string, text: string, html: boolean = false): Promise { + const normalizedFrom = from === 'auto' ? 'auto' : normalizeLanguageCode(from); + const normalizedTo = normalizeLanguageCode(to); + return translateWithPivot(normalizedFrom, normalizedTo, text, html); + } + + /** + * Detect language of the text + * @param text Text to analyze + */ + async detect(text: string): Promise { + return detectLanguage(text); + } + + /** + * Ensure model is downloaded for a language pair + * @param from Source language + * @param to Target language + */ + async downloadModel(from: string, to: string): Promise { + const normalizedFrom = normalizeLanguageCode(from); + const normalizedTo = normalizeLanguageCode(to); + await downloadModel(normalizedTo, normalizedFrom); + } + + /** + * Clean up all loaded engines and release memory + */ + async close(): Promise { + cleanupAllEngines(); + } + + /** + * Get current configuration + */ + getConfig(): Config { + return getConfig(); + } +} diff --git a/src/services/detector.ts b/src/services/detector.ts index 0ecf2d7..af162bb 100644 --- a/src/services/detector.ts +++ b/src/services/detector.ts @@ -1,3 +1,5 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; import loadCLD2 from '@/lib/cld2/cld2.js'; import wasmPath from '@/lib/cld2/cld2.wasm' with { type: 'file' }; import * as logger from '@/logger/index.js'; @@ -28,7 +30,10 @@ async function initCLD(): Promise { try { logger.debug('Initializing CLD2 language detector'); - const wasmBuffer = await readFile(wasmPath); + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const absoluteWasmPath = path.resolve(currentDir, wasmPath); + + const wasmBuffer = await readFile(absoluteWasmPath); const module: any = await loadCLD2({ print: (msg: string) => logger.debug(`[CLD2]: ${msg}`), diff --git a/src/services/engine.ts b/src/services/engine.ts index cc1ec8d..c584acb 100644 --- a/src/services/engine.ts +++ b/src/services/engine.ts @@ -1,4 +1,5 @@ import path from 'path'; +import { fileURLToPath } from 'url'; import { readFile } from 'fs/promises'; import { TranslationEngine } from '@/core/engine.js'; import { createResourceLoader } from '@/core/factory.js'; @@ -69,8 +70,11 @@ async function getOrCreateSingleEngine( const engine = new TranslationEngine(); const loader = createResourceLoader(); - logger.debug(`Loading WASM from: ${wasmPath}`); - const wasmBinary = await readFile(wasmPath); + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const absoluteWasmPath = path.resolve(currentDir, wasmPath); + + logger.debug(`Loading WASM from: ${absoluteWasmPath}`); + const wasmBinary = await readFile(absoluteWasmPath); logger.debug(`WASM loaded, size: ${wasmBinary.byteLength} bytes`); const bergamotModule = await loader.loadBergamotModule(wasmBinary, loadBergamot); diff --git a/tsconfig.lib.json b/tsconfig.lib.json new file mode 100644 index 0000000..0318b73 --- /dev/null +++ b/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "emitDeclarationOnly": true, + "noEmit": false, + "types": ["node"] + }, + "include": [], + "files": ["src/index.ts", "src/globals.d.ts"] +}