ngx-i18nsupport
Version:
Some tooling to be used with the Angular 2 i18n workflow
157 lines (146 loc) • 8 kB
text/typescript
import {isNullOrUndefined} from 'util';
import {Observable, forkJoin, of} from 'rxjs';
import {map, catchError} from 'rxjs/operators';
import * as entityDecoderLib from 'he';
import {
IICUMessage, IICUMessageTranslation, INormalizedMessage, ITranslationMessagesFile, ITransUnit,
STATE_NEW
} from 'ngx-i18nsupport-lib/dist';
import {AutoTranslateService} from './auto-translate-service';
import {AutoTranslateResult} from './auto-translate-result';
import {AutoTranslateSummaryReport} from './auto-translate-summary-report';
/**
* Created by martin on 07.07.2017.
* Service to autotranslate Transunits via Google Translate.
*/
export class XliffMergeAutoTranslateService {
private autoTranslateService: AutoTranslateService;
constructor(apikey: string) {
this.autoTranslateService = new AutoTranslateService(apikey);
}
/**
* Auto translate file via Google Translate.
* Will translate all new units in file.
* @param from
* @param to
* @param languageSpecificMessagesFile
* @return a promise with the execution result as a summary report.
*/
public autoTranslate(from: string, to: string, languageSpecificMessagesFile: ITranslationMessagesFile): Observable<AutoTranslateSummaryReport> {
return forkJoin([
this.doAutoTranslateNonICUMessages(from, to, languageSpecificMessagesFile),
...this.doAutoTranslateICUMessages(from, to, languageSpecificMessagesFile)])
.pipe(
map((summaries: AutoTranslateSummaryReport[]) => {
const summary = summaries[0];
for (let i = 1; i < summaries.length; i++) {
summary.merge(summaries[i]);
}
return summary;
}));
}
/**
* Collect all units that are untranslated.
* @param languageSpecificMessagesFile
* @return {ITransUnit[]}
*/
private allUntranslatedTUs(languageSpecificMessagesFile: ITranslationMessagesFile): ITransUnit[] {
// collect all units, that should be auto translated
const allUntranslated: ITransUnit[] = [];
languageSpecificMessagesFile.forEachTransUnit((tu) => {
if (tu.targetState() === STATE_NEW) {
allUntranslated.push(tu);
}
});
return allUntranslated;
}
private doAutoTranslateNonICUMessages(from: string, to: string, languageSpecificMessagesFile: ITranslationMessagesFile): Observable<AutoTranslateSummaryReport> {
const allUntranslated: ITransUnit[] = this.allUntranslatedTUs(languageSpecificMessagesFile);
const allTranslatable = allUntranslated.filter((tu) => isNullOrUndefined(tu.sourceContentNormalized().getICUMessage()));
const allMessages: string[] = allTranslatable.map((tu) => {
return tu.sourceContentNormalized().asDisplayString();
});
return this.autoTranslateService.translateMultipleStrings(allMessages, from, to)
.pipe(
// #94 google translate might return &#.. entity refs, that must be decoded
map((translations: string[]) => translations.map(encodedTranslation => entityDecoderLib.decode(encodedTranslation))),
map((translations: string[]) => {
const summary = new AutoTranslateSummaryReport(from, to);
summary.setIgnored(allUntranslated.length - allTranslatable.length);
for (let i = 0; i < translations.length; i++) {
const tu = allTranslatable[i];
const translationText = translations[i];
const result = this.autoTranslateNonICUUnit(tu, translationText);
summary.addSingleResult(tu, result);
}
return summary;
}),
catchError((err) => {
const failSummary = new AutoTranslateSummaryReport(from, to);
failSummary.setError(err.message, allMessages.length);
return of(failSummary);
}));
}
private doAutoTranslateICUMessages(from: string, to: string, languageSpecificMessagesFile: ITranslationMessagesFile): Observable<AutoTranslateSummaryReport>[] {
const allUntranslated: ITransUnit[] = this.allUntranslatedTUs(languageSpecificMessagesFile);
const allTranslatableICU = allUntranslated.filter((tu) => !isNullOrUndefined(tu.sourceContentNormalized().getICUMessage()));
return allTranslatableICU.map((tu) => {
return this.doAutoTranslateICUMessage(from, to, tu);
});
}
/**
* Translate single ICU Messages.
* @param from
* @param to
* @param tu transunit to translate (must contain ICU Message)
* @return {Observable<AutoTranslateSummaryReport>}
*/
private doAutoTranslateICUMessage(from: string, to: string, tu: ITransUnit): Observable<AutoTranslateSummaryReport> {
const icuMessage: IICUMessage = tu.sourceContentNormalized().getICUMessage();
const categories = icuMessage.getCategories();
// check for nested ICUs, we do not support that
if (categories.find((category) => !isNullOrUndefined(category.getMessageNormalized().getICUMessage()))) {
const summary = new AutoTranslateSummaryReport(from, to);
summary.setIgnored(1);
return of(summary);
}
const allMessages: string[] = categories.map((category) => category.getMessageNormalized().asDisplayString());
return this.autoTranslateService.translateMultipleStrings(allMessages, from, to)
.pipe(
// #94 google translate might return &#.. entity refs, that must be decoded
map((translations: string[]) => translations.map(encodedTranslation => entityDecoderLib.decode(encodedTranslation))),
map((translations: string[]) => {
const summary = new AutoTranslateSummaryReport(from, to);
const icuTranslation: IICUMessageTranslation = {};
for (let i = 0; i < translations.length; i++) {
const translationText = translations[i];
icuTranslation[categories[i].getCategory()] = translationText;
}
const result = this.autoTranslateICUUnit(tu, icuTranslation);
summary.addSingleResult(tu, result);
return summary;
}), catchError((err) => {
const failSummary = new AutoTranslateSummaryReport(from, to);
failSummary.setError(err.message, allMessages.length);
return of(failSummary);
}));
}
private autoTranslateNonICUUnit(tu: ITransUnit, translatedMessage: string): AutoTranslateResult {
return this.autoTranslateUnit(tu, tu.sourceContentNormalized().translate(translatedMessage));
}
private autoTranslateICUUnit(tu: ITransUnit, translation: IICUMessageTranslation): AutoTranslateResult {
return this.autoTranslateUnit(tu, tu.sourceContentNormalized().translateICUMessage(translation));
}
private autoTranslateUnit(tu: ITransUnit, translatedMessage: INormalizedMessage): AutoTranslateResult {
const errors = translatedMessage.validate();
const warnings = translatedMessage.validateWarnings();
if (!isNullOrUndefined(errors)) {
return new AutoTranslateResult(false, 'errors detected, not translated');
} else if (!isNullOrUndefined(warnings)) {
return new AutoTranslateResult(false, 'warnings detected, not translated');
} else {
tu.translate(translatedMessage);
return new AutoTranslateResult(true, null); // success
}
}
}