UNPKG

angular-t9n

Version:

A translation tool for Angular i18n

165 lines (151 loc) 5.96 kB
import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { WsAdapter } from '@nestjs/platform-ws'; import { readdirSync, readFileSync, writeFileSync } from 'fs'; import { dirname, join, relative, resolve } from 'path'; import { AppModule, Options, PersistenceStrategy, SerializationOptions, SerializationStrategy, TargetInfo, TargetPathBuilder, TranslationDeserializer, TranslationSerializer, TranslationSource, TranslationTargetRegistry, WorkspaceHost, Xlf2Deserializer, Xlf2Serializer, XlfDeserializer, XlfSerializer, XmlParser, } from '../server'; import { AsyncWorkspaceHost } from './async-workspace-host'; import { PatternPersistenceStrategy } from './pattern-persistence-strategy'; export function init(name: string = 't9n.conf.json') { name = name.endsWith('.json') ? name : `${name}.json`; const options: Options & { $schema: string } = { $schema: 'https://raw.githubusercontent.com/kyubisation/angular-t9n/master/t9n.schema.json', translationFile: 'messages.xlf', targetTranslationPath: '', includeContextInTarget: false, port: 4300, }; writeFileSync(join(process.cwd(), name), JSON.stringify(options, null, 2), 'utf8'); } export async function runT9nStandalone(configFile: string) { const configFilePath = resolve(configFile); let config: Options; try { const content = readFileSync(configFilePath, 'utf8'); config = JSON.parse(content); } catch (e) { throw new Error(`Unable to parse file ${configFilePath}`); } await t9nStandalone(config); } export async function t9nStandalone(options: Options, currentWorkingDirectory?: string) { const host = new AsyncWorkspaceHost(); const workspaceRoot = resolve(currentWorkingDirectory || process.cwd()); const sourceFile = join(workspaceRoot, options.translationFile); const targetTranslationPath = options.targetTranslationPath || dirname(options.translationFile); const targetDirectory = join(workspaceRoot, targetTranslationPath); if (!(await host.isFile(sourceFile))) { throw new Error(`${options.translationFile} does not exist or is not a file!`); } else if (!(await host.isDirectory(targetDirectory))) { throw new Error(`targetTranslationPath ${targetTranslationPath} is not a valid directory!`); } const xliffVersion = await detectXliffVersion(); console.log(`Detected version ${xliffVersion} of XLIFF`); const targetPathBuilder = new TargetPathBuilder(targetDirectory, sourceFile); console.log(`Loading translations. Depending on the amount, this might take a moment.`); const app = await NestFactory.create( AppModule.forRoot([ { provide: TargetPathBuilder, useValue: targetPathBuilder }, { provide: WorkspaceHost, useValue: host }, { provide: TargetInfo, useFactory: (source: TranslationSource) => new TargetInfo('-', options.translationFile, source.language), inject: [TranslationSource], }, { provide: SerializationOptions, useValue: options }, { provide: TranslationDeserializer, useExisting: xliffVersion === '1.2' ? XlfDeserializer : Xlf2Deserializer, }, { provide: TranslationSerializer, useExisting: xliffVersion === '1.2' ? XlfSerializer : Xlf2Serializer, }, { provide: TranslationSource, useFactory: TRANSLATION_SOURCE_FACTORY, inject: [SerializationStrategy], }, { provide: TranslationTargetRegistry, useFactory: TRANSLATION_TARGET_REGISTRY_FACTORY, inject: [TranslationSource, SerializationStrategy, PersistenceStrategy], }, { provide: PersistenceStrategy, useClass: PatternPersistenceStrategy }, ]), { cors: true, logger: ['error', 'warn'], }, ); app.setGlobalPrefix('api'); app.useWebSocketAdapter(new WsAdapter(app)); app.useGlobalPipes(new ValidationPipe({ skipMissingProperties: true, whitelist: true })); await app.listen(options.port ?? 4300, () => console.log(`Translation server started: http://localhost:${options.port}\n`), ); return new Promise(() => {}); async function detectXliffVersion(): Promise<'2.0' | '1.2'> { const content = await host.readFile(sourceFile); const doc = new XmlParser().parse(content); const version = doc.documentElement.getAttribute('version'); if (doc.documentElement.tagName !== 'xliff') { throw new Error('Only xliff is supported!'); } else if (version !== '2.0' && version !== '1.2') { throw new Error('Unsupported xliff version!'); } else { return version; } } async function TRANSLATION_SOURCE_FACTORY( serializationStrategy: SerializationStrategy, ): Promise<TranslationSource> { const result = await serializationStrategy.deserializeSource(sourceFile); return new TranslationSource(result.language, result.unitMap); } async function TRANSLATION_TARGET_REGISTRY_FACTORY( source: TranslationSource, serializationStrategy: SerializationStrategy, persistenceStrategy: PersistenceStrategy, ): Promise<TranslationTargetRegistry> { const targetRegistry = new TranslationTargetRegistry(source, persistenceStrategy); await Promise.all( readdirSync(targetDirectory, { withFileTypes: true }) .filter((d) => d.isFile()) .map(async (d) => { const targetPath = join(targetDirectory, d.name); if (sourceFile === targetPath) { return; } try { const result = await serializationStrategy.deserializeTarget(targetPath); if (targetPath !== targetPathBuilder.createPath(result.language)) { return; } console.log(`Detected ${relative(workspaceRoot, targetPath)}`); targetRegistry.register(result.language, result.unitMap); } catch {} }), ); return targetRegistry; } }