ngx-i18nsupport
Version:
Some tooling to be used with the Angular 2 i18n workflow
174 lines (161 loc) • 6.43 kB
text/typescript
import {ITranslationMessagesFile, ITransUnit, NORMALIZATION_FORMAT_NGXTRANSLATE} from 'ngx-i18nsupport-lib';
import {FileUtil} from '../common/file-util';
import {isNullOrUndefined} from 'util';
import {NgxTranslateExtractionPattern} from './ngx-translate-extraction-pattern';
/**
* Created by roobm on 15.03.2017.
* A tool for extracting messages in ngx-translate format.
* Generates a json-file to be used with ngx-translate.
*/
/**
* The interface used for translations in ngx-translate.
* A hash that contains either the translation or another hash.
*/
interface NgxTranslations {
[id: string]: NgxTranslations | string;
}
/**
* internal,
* a message with id (a dot-separated string).
*/
interface NgxMessage {
id: string; // dot separated name, e.g. "myapp.service1.message1"
message: string; // the message, placeholder are in {{n}} syntax, e.g. "a test with value: {{0}}
}
export class NgxTranslateExtractor {
public static DefaultExtractionPattern = '@@|ngx-translate';
private extractionPattern: NgxTranslateExtractionPattern;
/**
* Check, wether extractionPattern has valid syntax.
* @param {string} extractionPatternString
* @return {string} null, if pattern is ok, string describing the error, if it is not ok.
*/
public static checkPattern(extractionPatternString: string): string {
try {
new NgxTranslateExtractionPattern(extractionPatternString);
} catch (error) {
return error.message;
}
return null;
}
public static extract(messagesFile: ITranslationMessagesFile, extractionPattern: string, outputFile: string) {
new NgxTranslateExtractor(messagesFile, extractionPattern).extractTo(outputFile);
}
constructor(private messagesFile: ITranslationMessagesFile, extractionPatternString: string) {
this.extractionPattern = new NgxTranslateExtractionPattern(extractionPatternString);
}
/**
* Extact messages and write them to a file.
* @param outputFile
*/
public extractTo(outputFile: string) {
let translations: NgxTranslations = this.toNgxTranslations(this.extract());
if (translations && Object.keys(translations).length > 0) {
FileUtil.replaceContent(outputFile, JSON.stringify(translations, null, 4), 'UTF-8')
} else {
if (FileUtil.exists(outputFile)) {
FileUtil.deleteFile(outputFile);
}
}
}
/**
* Extract messages and convert them to ngx translations.
* @return the translation objects.
*/
private extract(): NgxMessage[] {
let result: NgxMessage[] = [];
this.messagesFile.forEachTransUnit((tu: ITransUnit) => {
let ngxId = this.ngxTranslateIdFromTU(tu);
if (ngxId) {
let messagetext = tu.targetContentNormalized().asDisplayString(NORMALIZATION_FORMAT_NGXTRANSLATE);
result.push({id: ngxId, message: messagetext});
}
});
return result;
}
/**
* Check, wether this tu should be extracted for ngx-translate usage, and return its id for ngx-translate.
* There are 2 possibilities:
* 1. description is set to "ngx-translate" and meaning contains the id.
* 2. id is explicitly set to a string.
* @param tu
* @return an ngx id or null, if this tu should not be extracted.
*/
private ngxTranslateIdFromTU(tu: ITransUnit): string {
if (this.isExplicitlySetId(tu.id)) {
if (this.extractionPattern.isExplicitIdMatched(tu.id)) {
return tu.id;
} else {
return null;
}
}
let description = tu.description();
if (description && this.extractionPattern.isDescriptionMatched(description)) {
return tu.meaning();
}
}
/**
* Test, wether ID was explicitly set (via i18n="@myid).
* Just heuristic, an ID is explicitly, if it does not look like a generated one.
* @param id
* @return {boolean}
*/
private isExplicitlySetId(id: string): boolean {
if (isNullOrUndefined(id)) {
return false;
}
// generated IDs are either decimal or sha1 hex
let reForGeneratedId = /^[0-9a-f]{11,}$/;
return !reForGeneratedId.test(id);
}
/**
* Convert list of relevant TUs to ngx translations object.
* @param msgList
*/
private toNgxTranslations(msgList: NgxMessage[]): NgxTranslations {
let translationObject: NgxTranslations = {};
msgList.forEach((msg: NgxMessage) => {
this.putInTranslationObject(translationObject, msg);
});
return translationObject;
}
/**
* Put a new messages into the translation data object.
* If you add, e.g. "{id: 'myapp.example', message: 'test'}",
* the translation object will then contain an object myapp that has property example:
* {myapp: {
* example: 'test'
* }}
* @param translationObject
* @param msg
*/
private putInTranslationObject(translationObject: NgxTranslations, msg: NgxMessage) {
let firstPartOfId: string;
let restOfId: string;
let indexOfDot = msg.id.indexOf('.');
if (indexOfDot == 0 || indexOfDot == (msg.id.length - 1)) {
throw new Error('bad nxg-translate id "' + msg.id + '"');
}
if (indexOfDot < 0) {
firstPartOfId = msg.id;
restOfId = '';
} else {
firstPartOfId = msg.id.substring(0, indexOfDot);
restOfId = msg.id.substring(indexOfDot + 1);
}
let object = translationObject[firstPartOfId];
if (isNullOrUndefined(object)) {
if (restOfId === '') {
translationObject[firstPartOfId] = msg.message;
return;
}
object = {};
translationObject[firstPartOfId] = object;
} else {
if (restOfId === '') {
throw new Error('duplicate id praefix "' + msg.id + '"');
}
}
this.putInTranslationObject(<NgxTranslations> object, {id: restOfId, message: msg.message});
}
}