angular-t9n
Version:
A translation tool for Angular i18n
165 lines (151 loc) • 5.96 kB
text/typescript
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;
}
}