ngx-i18nsupport
Version:
Some tooling to be used with the Angular 2 i18n workflow
237 lines (215 loc) • 8.26 kB
text/typescript
import {format} from 'util';
import * as request from 'request';
import {Observable} from 'rxjs';
import {of, forkJoin, throwError} from 'rxjs';
import {map} from 'rxjs/operators';
/**
* Created by roobm on 03.07.2017.
* Low Level Service to call Google Translate.
*/
/**
* Types form google translate api.
*/
interface GetSupportedLanguagesRequest {
target: string; // The language to use to return localized, human readable names of supported\nlanguages.
}
interface LanguagesResource {
language: string; // code of the language
name: string; // human readable name (in target language)
}
interface LanguagesListResponse {
languages: LanguagesResource[];
}
interface TranslateTextRequest {
q: string[]; // The input texts to translate
target: string; // The language to use for translation of the input text
source: string; // The language of the source text
format?: string; // "html" (default) or "text"
model?: string; // see public documentation
}
interface TranslationsResource {
detectedSourceLanguage?: string;
model?: string;
translatedText: string;
}
interface TranslationsListResponse {
translations: TranslationsResource[];
}
interface InternalRequestResponse {
response: request.RequestResponse;
body: any;
}
const MAX_SEGMENTS = 128;
export class AutoTranslateService {
private _request: request.RequestAPI<request.Request, request.CoreOptions, request.RequiredUriUrl>;
_rootUrl: string;
_apiKey: string;
constructor(apiKey: string) {
this._request = request;
this._apiKey = apiKey;
this._rootUrl = 'https://translation.googleapis.com/';
}
/**
* Change API key (just for tests).
* @param apikey
*/
public setApiKey(apikey: string) {
this._apiKey = apikey;
}
/**
* Translate an array of messages at once.
* @param messages the messages to be translated
* @param from source language code
* @param to target language code
* @return Observable with translated messages or error
*/
public translateMultipleStrings(messages: string[], from: string, to: string): Observable<string[]> {
// empty array needs no translation and always works ... (#78)
if (messages.length == 0) {
return of([]);
}
if (!this._apiKey) {
return throwError('cannot autotranslate: no api key');
}
if (!from || !to) {
return throwError('cannot autotranslate: source and target language must be set');
}
from = AutoTranslateService.stripRegioncode(from);
to = AutoTranslateService.stripRegioncode(to);
const allRequests: Observable<string[]>[] = this.splitMessagesToGoogleLimit(messages).map((partialMessages: string[]) => {
return this.limitedTranslateMultipleStrings(partialMessages, from, to);
});
return forkJoin(allRequests).pipe(
map((allTranslations: string[][]) => {
let all = [];
for (let i = 0; i < allTranslations.length; i++) {
all = all.concat(allTranslations[i]);
}
return all;
}));
}
private splitMessagesToGoogleLimit(messages: string[]): string[][] {
if (messages.length <= MAX_SEGMENTS) {
return [messages];
}
const result = [];
let currentPackage = [];
let packageSize = 0;
for (let i = 0; i < messages.length; i++) {
currentPackage.push(messages[i]);
packageSize++;
if (packageSize >= MAX_SEGMENTS) {
result.push(currentPackage);
currentPackage = [];
packageSize = 0;
}
}
if (currentPackage.length > 0) {
result.push(currentPackage);
}
return result;
}
/**
* Return translation request, but messages must be limited to google limits.
* Not more that 128 single messages.
* @param messages
* @param from
* @param to
* @return {Observable<string[]>} the translated strings
*/
private limitedTranslateMultipleStrings(messages: string[], from: string, to: string): Observable<string[]> {
const realUrl = this._rootUrl + 'language/translate/v2' + '?key=' + this._apiKey;
const translateRequest: TranslateTextRequest = {
q: messages,
target: to,
source: from,
};
const options = {
url: realUrl,
body: translateRequest,
json: true,
// proxy: 'http://127.0.0.1:8888' To set a proxy use env var HTTPS_PROXY
};
return this.post(realUrl, options).pipe(
map((data) => {
const body: any = data.body;
if (!body) {
throw new Error('no result received');
}
if (body.error) {
if (body.error.code === 400) {
if (body.error.message === 'Invalid Value') {
throw new Error(format('Translation from "%s" to "%s" not supported', from, to));
}
throw new Error(format('Invalid request: %s', body.error.message));
} else {
throw new Error(format('Error %s: %s', body.error.code, body.error.message));
}
}
const result = body.data;
return result.translations.map((translation: TranslationsResource) => {
return translation.translatedText;
});
}));
}
/**
* Strip region code and convert to lower
* @param lang
* @return {string} lang without region code and in lower case.
*/
public static stripRegioncode(lang: string): string {
const langLower = lang.toLowerCase();
for(let i = 0; i < langLower.length; i++) {
const c = langLower.charAt(i);
if (c < 'a' || c > 'z') {
return langLower.substring(0, i);
}
}
return langLower;
}
/**
* Function to do a POST HTTP request
*
* @param uri {string}
* @param options {CoreOptions}
*
* @return {Observable<InternalRequestResponse>}
*/
post(uri: string, options?: request.CoreOptions): Observable<InternalRequestResponse> {
return <Observable<InternalRequestResponse>> this._call.apply(this, [].concat('post', <string> uri,
<request.CoreOptions> Object.assign({}, options || {})));
}
/**
* Function to do a HTTP request for given method
*
* @param method {string}
* @param uri {string}
* @param options {CoreOptions}
*
* @return {Observable<InternalRequestResponse>}
*
* @private
*/
private _call(method: string, uri: string, options?: request.CoreOptions): Observable<InternalRequestResponse> {
return <Observable<InternalRequestResponse>> Observable.create((observer) => {
// build params array
const params = [].concat(<string> uri, <request.CoreOptions> Object.assign({}, options || {}),
<RequestCallback>(error: any, response: request.RequestResponse, body: any) => {
if (error) {
return observer.error(error);
}
observer.next(<InternalRequestResponse> Object.assign({}, {
response: <request.RequestResponse> response,
body: <any> body
}));
observer.complete();
});
// _call request method
try {
this._request[<string> method].apply(<request.RequestAPI<request.Request, request.CoreOptions, request.RequiredUriUrl>> this._request, params);
} catch (error) {
observer.error(error);
}
});
}
}