Skip to content

Instantly share code, notes, and snippets.

@mohlendo
Last active December 11, 2019 01:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mohlendo/90c834f90bc084af2b869ca36a20712f to your computer and use it in GitHub Desktop.
Save mohlendo/90c834f90bc084af2b869ca36a20712f to your computer and use it in GitHub Desktop.
Angular i18n translations outside a template - JIT only
import { Component } from '@angular/core'
@Component({
selector: 'i18n',
moduleId: module.id,
template: `<span i18n="@@foobar">Hello {{placeholder}}!</span>`
})
export class I18NComponent {
placeholder: any
}
import { Inject, Injectable, Optional, TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular/core'
import { HtmlParser, I18NHtmlParser, Xliff, Xliff2, Xmb } from '@angular/compiler'
/**
* Service that fills the gap between i18n templates and dynamic messages.
* Add new messages to the template of the {@see I18NComponent}.
* Only simple interpolations are supported no ICU stuff.
*/
@Injectable()
export class I18NService {
private readonly parser: I18NHtmlParser
private readonly translations: {
locale: string;
i18nNodesByMsgId: {
[msgId: string]: any;
};
}
// simple cache for the prepared patterns
private readonly patternCache: { [key: string]: string } = {}
constructor (
@Optional() @Inject(TRANSLATIONS) source: string = null,
@Inject(TRANSLATIONS_FORMAT) format = 'xlf2'
) {
if (source) {
let serializer
switch (format) {
case 'xlf2':
serializer = new Xliff2()
break
case 'xmb':
serializer = new Xmb()
break
case 'xlf':
serializer = new Xliff()
break
}
this.translations = serializer.load(source, '')
this.parser = new I18NHtmlParser(new HtmlParser(), source)
} else {
this.translations = {locale: 'en', i18nNodesByMsgId: {}}
}
}
private static toParseString (nodes: any[]): string {
let interpolationIndex = 0
return nodes.reduce((prev, node) => {
if (node.hasOwnProperty('name')) {
return `${prev}{{${interpolationIndex++}}}`
}
if (node.hasOwnProperty('value')) {
return `${prev}${node.value}`
}
}, '').trim()
}
private static interpolate (pattern: string, interpolation: any[]) {
// this is actually a static message
if (interpolation.length === 0) {
return pattern
}
let compiled = ''
compiled += pattern.replace(/{{(\w+)}}/g, (match, key) => {
if (interpolation[key] !== undefined) {
match = match.replace(`{{${key}}}`, interpolation[key])
}
return match
})
return compiled
}
/**
* Returns the translated message for the given key.
* The order of the interpolation must match the order in the message. Don't change it during translation.
* @param {string} key translation key
* @param {any[]} interpolation the list of interpolations
* @return {string} the resulting message
*/
get (key: string, interpolation: any[] = []): string {
if (!key) {
throw new Error('key cannot be empty')
}
const nodes = this.translations.i18nNodesByMsgId[key]
if (!nodes) {
console.warn(`Missing translation for message "${key}"`)
return key
}
// check if we have a cache-entry
let pattern = this.patternCache[key]
if (!pattern) {
const parseTree = this.parser.parse(`<div i18n="@@${key}">${I18NService.toParseString(nodes)}</div>`, '')
pattern = parseTree.rootNodes[0]['children'][0].value
// cache the pattern
this.patternCache[key] = pattern
}
return I18NService.interpolate(pattern, interpolation)
}
}
import { Component } from '@angular/core'
@Component({
selector: 'test',
moduleId: module.id
})
export class I18NComponent {
constructor(readonly i18nService: I18NService) {
console.log(i18nService.get('foobar', ['World'])
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment