├── index.js ├── .gitignore ├── .eslintrc.js ├── d.tsconfig.json ├── .prettierrc.js ├── server.js ├── jsconfig.json ├── .editorconfig ├── azure-pipelines.yml ├── test ├── tsc │ ├── tsconfig.json │ └── test.ts └── unit │ └── lit-i18n.js ├── package.json ├── readme.md ├── example └── index.html └── src └── lit-i18n.js /index.js: -------------------------------------------------------------------------------- 1 | export * from './src/lit-i18n.js'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | index.d.ts 4 | src/lit-i18n.d.ts 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // ESLint configuration 2 | // http://eslint.org/docs/user-guide/configuring 3 | 4 | module.exports = { 5 | extends: ['eslint-config-colscott'] 6 | }; -------------------------------------------------------------------------------- /d.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./index.js", "src/**/*"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true 7 | } 8 | } -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Prettier configuration 2 | // https://prettier.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | printWidth: 120, 6 | singleQuote: true, 7 | trailingComma: 'all' 8 | }; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const app = express(); 4 | 5 | app.use('/', express.static('./')); 6 | 7 | app.listen(process.env.PORT || 3001); 8 | 9 | console.info(`server started on: http://localhost:${process.env.PORT || 3000}`); 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["esnext.array", "esnext", "es2017", "dom"], 5 | "rootDir": "./", 6 | "moduleResolution": "node" 7 | }, 8 | "include": [ 9 | "index.js", 10 | "src/**/*.js" 11 | ], 12 | "typeAcquisition": { 13 | "include": [ 14 | "i18next" 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs. 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference. 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged. 14 | end_of_line = crlf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | # editorconfig-tools is unable to ignore longs strings or urls. 20 | max_line_length = null -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | steps: 13 | - task: NodeTool@0 14 | inputs: 15 | versionSpec: '14.x' 16 | displayName: 'Install Node.js' 17 | 18 | - script: | 19 | npm install 20 | displayName: 'npm install' 21 | 22 | - script: | 23 | npm run test:ci 24 | displayName: 'npm run test:ci' 25 | -------------------------------------------------------------------------------- /test/tsc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "es2020", 5 | "lib": ["es2021", "DOM", "DOM.Iterable"], 6 | "declaration": true, 7 | "declarationMap": true, 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "outDir": "./dist", 11 | "rootDir": "./", 12 | "strict": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": true, 18 | "noImplicitThis": true, 19 | "moduleResolution": "node", 20 | "allowSyntheticDefaultImports": true, 21 | "experimentalDecorators": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "noImplicitOverride": true, 24 | }, 25 | "include": ["**/*.ts"], 26 | "exclude": [] 27 | } 28 | -------------------------------------------------------------------------------- /test/tsc/test.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | import { initLitI18n, translate as t } from '../../index'; 3 | 4 | i18next.use(initLitI18n).init({ 5 | lng: 'en', 6 | resources: { 7 | en: { 8 | translation: { 9 | hello: 'en-hello', 10 | whatishow: '{{what}} is {{how}}', 11 | datamodel: '{{person.name}} is a {{person.age}} year old and is male: {{person.male}}', 12 | }, 13 | }, 14 | fr: { 15 | translation: { 16 | hello: 'fr-hello', 17 | whatishow: '{{what}} est {{how}}', 18 | datamodel: '{{person.name}} a {{person.age}} ans et est un homme: {{person.male}}', 19 | }, 20 | }, 21 | }, 22 | }); 23 | 24 | // eslint-disable-next-line require-jsdoc 25 | console.log(t('test')); 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-i18n", 3 | "version": "4.1.0", 4 | "description": "lit-element based i18n solution backed by i18next", 5 | "main": "index.js", 6 | "files": [ 7 | "/index.js", 8 | "/index.d.ts", 9 | "/src/", 10 | "/dist/" 11 | ], 12 | "scripts": { 13 | "start": "web-dev-server --node-resolve --preserve-symlinks", 14 | "test": "npm run test:watch", 15 | "test:ci": "npm run test:unit", 16 | "test:coverage": "npm run test:unit -- --coverage", 17 | "test:watch": "npm run test:unit -- --watch", 18 | "test:unit": "web-test-runner \"test/unit/**/*.js\" --node-resolve --preserve-symlinks", 19 | "prepublishOnly": "npm run generateDeclarations", 20 | "generateDeclarations": "node ./node_modules/typescript/bin/tsc -p d.tsconfig.json", 21 | "removeDeclarations": "rimraf ./src/**/*.d.ts", 22 | "tsc:test": "node ./node_modules/typescript/bin/tsc -p ./test/tsc/tsconfig.json", 23 | "tsc:test:watch": "node ./node_modules/typescript/bin/tsc -p ./test/tsc/tsconfig.json --watch" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/colscott/lit-i18n.git" 28 | }, 29 | "keywords": [ 30 | "i18n", 31 | "lit-element", 32 | "lit-html", 33 | "i18n", 34 | "web", 35 | "component", 36 | "lit html i18n", 37 | "i18next" 38 | ], 39 | "author": "colin scott", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/colscott/lit-i18n/issues" 43 | }, 44 | "homepage": "https://github.com/colscott/lit-i18n#readme", 45 | "devDependencies": { 46 | "@esm-bundle/chai": "^4.3.4-fix.0", 47 | "@web/dev-server": "^0.1.25", 48 | "@web/test-runner": "^0.13.20", 49 | "dev-lib-colscott": "^2.0.0", 50 | "i18next": ">=21.3.3", 51 | "lit-html": ">=2.0.1", 52 | "typescript": "^5.2.2" 53 | }, 54 | "peerDependencies": { 55 | "i18next": ">=21.3.3", 56 | "lit-html": ">=2.0.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # lit-i18n 2 | i18next lit-html directive (could possible add other i18n backends). 3 | 4 | [![Build Status](https://dev.azure.com/colscott/lit-i18n/_apis/build/status/colscott.lit-i18n?branchName=master)](https://dev.azure.com/colscott/lit-i18n/_build/latest?definitionId=2&branchName=master) 5 | 6 | ## Install 7 | npm install lit-i18n 8 | ## Usage 9 | ### Initialization 10 | Pass in the `initLitI18n` as an i18next plugin before initializing i18next with a [config](https://www.i18next.com/overview/configuration-options). 11 | ```js 12 | import i18next from 'i18next'; 13 | import { initLitI18n } from 'lit-i18n'; 14 | 15 | i18next.use(initLitI18n).init({...}); 16 | ``` 17 | 18 | ### Performing Translations 19 | Use the lit-i18n translate directive to perform translations in lit-html templates. 20 | 21 | The translate directive has the same signature and functionality as the i18next [t method](https://www.i18next.com/overview/api#t). 22 | 23 | ```js 24 | import { translate as t } from 'lit-i18n'; 25 | import { html } from 'lit-html'; 26 | 27 | const template1 = html`${t('hello')}`; 28 | const template2 = html`${t('whatishow', { what: 'i18next', how: 'great' })}`; 29 | const template3 = html`${t('personDescription', { person: { name: fred, age: 34, male: true} })}`; 30 | ``` 31 | 32 | ### LitElement example 33 | ```js 34 | import i18next from 'i18next'; 35 | import { translate as t, initLitI18n } from 'lit-i18n'; 36 | import { LitElement, html } from 'lit'; 37 | 38 | // Initialize i18next with lit-i18n and config 39 | i18next.use(initLitI18n).init({ 40 | lng: 'en', 41 | resources: { 42 | en: { 43 | translation: { 44 | whatishow: '{{what}} is {{how}}', 45 | datamodel: '{{person.name}} is a {{person.age}} year old and is male: {{person.male}}', 46 | }, 47 | }, 48 | fr: { 49 | translation: { 50 | whatishow: '{{what}} est {{how}}', 51 | datamodel: '{{person.name}} a {{person.age}} ans et est un homme: {{person.male}}', 52 | }, 53 | }, 54 | }, 55 | }); 56 | 57 | // Create a LitElement that uses the lit-i18n translate directive 58 | customElements.define( 59 | 'test-i18n', 60 | class TestI18n extends LitElement { 61 | person = { 62 | name: 'Fred', 63 | age: 35, 64 | male: true, 65 | }; 66 | 67 | /** @returns {import('lit-html/lit-html').TemplateResult} */ 68 | render() { 69 | return html` 70 | 71 |
72 | 73 | ${t('datamodel', { person: this.person })} 74 | `; 75 | } 76 | }, 77 | ); 78 | ``` 79 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 42 | 93 | 94 | 95 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /test/unit/lit-i18n.js: -------------------------------------------------------------------------------- 1 | /* global describe, before, after, it */ 2 | /* Jasmine will be loaded by the test framework. No need to import it. */ 3 | import { expect } from '@esm-bundle/chai'; 4 | import i18next from 'i18next'; 5 | import { html, render } from 'lit-html'; 6 | import { translate as t, registry, registryCleanup, initLitI18n } from '../../src/lit-i18n.js'; 7 | 8 | /** i18next config */ 9 | const i18nInitialized = i18next.use(initLitI18n).init({ 10 | lng: 'en', 11 | debug: true, 12 | resources: { 13 | en: { 14 | translation: { 15 | key: 'Some translation', 16 | introduceself: 'My name is {{name}}', 17 | divtitle: 'Element title attribute', 18 | whatishow: '{{what}} is {{how}}', 19 | datamodel: '{{person.name}} is a {{person.age}} year old and is male: {{person.male}}', 20 | entername: 'Enter Name', 21 | }, 22 | }, 23 | fr: { 24 | translation: { 25 | key: 'Un peu de traduction', 26 | introduceself: "Je m'appelle {{name}}", 27 | divtitle: "Attribut de titre d'élément", 28 | whatishow: '{{what}} est {{how}}', 29 | datamodel: '{{person.name}} a {{person.age}} ans et est un homme: {{person.male}}', 30 | entername: 'Entrez le nom', 31 | }, 32 | }, 33 | }, 34 | }); 35 | 36 | /** test CustomElement */ 37 | customElements.define( 38 | 'i18n-test', 39 | /** Test custom element */ 40 | class I18nTestElement extends HTMLElement { 41 | /** Constructor */ 42 | constructor() { 43 | super(); 44 | } 45 | 46 | /** Hook in to render */ 47 | connectedCallback() { 48 | this.render(); 49 | } 50 | 51 | /** */ 52 | get renderTemplate() { 53 | return html` 54 | ${t('key')} 55 | `; 56 | } 57 | 58 | /** Perform render */ 59 | render() { 60 | render(this.renderTemplate, this); 61 | } 62 | }, 63 | ); 64 | 65 | /** @typedef {{name: string; age: number; male: boolean}} Person */ 66 | /** More complete example */ 67 | class I18nFull extends HTMLElement { 68 | /** @returns {Person} */ 69 | get person() { 70 | return this._person; 71 | } 72 | 73 | /** @param {Person} */ 74 | set person(value) { 75 | this._person = value; 76 | render(this.renderTemplate, this); 77 | } 78 | 79 | /** @inheritdoc */ 80 | constructor() { 81 | super(); 82 | } 83 | 84 | /** @inheritdoc */ 85 | connectedCallback() { 86 | if (!this.person) { 87 | this.person = { 88 | name: 'None', 89 | age: 0, 90 | male: false, 91 | }; 92 | } 93 | } 94 | 95 | /** @returns {import('lit-html/lit-html').TemplateResult} */ 96 | get renderTemplate() { 97 | return html` 98 | ${t('introduceself', { name: this.person.name })} 99 |
Div with translated title
100 |
101 | ${t('datamodel', { person: this.person })} 102 | 103 | `; 104 | } 105 | } 106 | customElements.define('i18n-full', I18nFull); 107 | 108 | /** @type {Array} */ 109 | let elements = []; 110 | 111 | /** Removes any added elements */ 112 | const tidyElements = () => { 113 | elements.forEach(e => e.parentElement && e.remove()); 114 | elements = []; 115 | }; 116 | 117 | /** 118 | * Adds a test element to the DOM 119 | * @param {string} tag 120 | * @returns {Element} 121 | */ 122 | const addElement = tag => { 123 | return elements[elements.push(document.body.appendChild(document.createElement(tag || 'i18n-test'))) - 1]; 124 | }; 125 | 126 | mocha.setup({ 127 | timeout: 15000, 128 | }); 129 | 130 | function nextThread() { 131 | return new Promise((res) => setTimeout(res)); 132 | } 133 | 134 | /** Tests */ 135 | describe('Translations', () => { 136 | it('Should perform translation', async () => { 137 | const elem = addElement('i18n-full'); 138 | if (elem instanceof I18nFull) { 139 | const personElem = elem.querySelector('.person'); 140 | await nextThread(); 141 | expect(personElem.innerText).to.equal('None is a 0 year old and is male: false'); 142 | elem.person = { 143 | name: 'Fred', 144 | age: 46, 145 | male: true, 146 | }; 147 | await nextThread(); 148 | expect(personElem.innerText).to.equal('Fred is a 46 year old and is male: true'); 149 | } 150 | }); 151 | 152 | it('Should translate attributes', async () => { 153 | const titleElem = addElement('i18n-full').querySelector('.title'); 154 | await nextThread(); 155 | expect(titleElem.title).to.equal('Element title attribute'); 156 | const intElem = addElement('i18n-full').querySelector('.title-interpolation'); 157 | await nextThread(); 158 | expect(intElem.title).to.equal('i18next is great'); 159 | const input = addElement('i18n-full').querySelector('.placeholder'); 160 | await nextThread(); 161 | expect(input.placeholder).to.equal('Enter Name'); 162 | }); 163 | }); 164 | 165 | describe('Events', () => { 166 | after(async () => { 167 | await i18next.changeLanguage('en'); 168 | }); 169 | it('Should react to language changes', async () => { 170 | const elem = addElement('i18n-full'); 171 | const titleElem = elem.querySelector('.title'); 172 | await nextThread(); 173 | expect(titleElem.title).to.equal('Element title attribute'); 174 | const intElem = elem.querySelector('.title-interpolation'); 175 | await nextThread(); 176 | expect(intElem.title).to.equal('i18next is great'); 177 | const input = elem.querySelector('.placeholder'); 178 | await nextThread(); 179 | expect(input.placeholder).to.equal('Enter Name'); 180 | const personElem = elem.querySelector('.person'); 181 | elem.person = { 182 | name: 'Fred', 183 | age: 46, 184 | male: true, 185 | }; 186 | await nextThread(); 187 | expect(personElem.innerText).to.equal('Fred is a 46 year old and is male: true'); 188 | 189 | await i18next.changeLanguage('fr'); 190 | await nextThread(); 191 | expect(titleElem.title).to.equal("Attribut de titre d'élément"); 192 | expect(intElem.title).to.equal('i18next est great'); 193 | expect(input.placeholder).to.equal('Entrez le nom'); 194 | expect(personElem.innerText).to.equal('Fred a 46 ans et est un homme: true'); 195 | }); 196 | }); 197 | 198 | describe('Garbage collection', () => { 199 | before(() => { 200 | tidyElements(); 201 | registryCleanup(); 202 | }); 203 | 204 | after(() => { 205 | tidyElements(); 206 | }); 207 | 208 | it('Parts references should be released for garbage collect', done => { 209 | expect(registry.size).to.equal(0); 210 | for (let i = 0, iLen = 1000; i < iLen; i++) { 211 | const elem = addElement(); 212 | if (i > 100) { 213 | elem.remove(); 214 | } 215 | } 216 | nextThread().then(() => { 217 | 218 | expect(registry.size).to.equal(1000); 219 | setTimeout(() => { 220 | expect(registry.size).to.equal(101); 221 | done(); 222 | }, 11000); 223 | }); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /src/lit-i18n.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { noChange } from 'lit-html'; 3 | import { directive, AsyncDirective } from 'lit-html/async-directive.js'; 4 | import { PartType } from 'lit-html/directive.js'; 5 | 6 | /** @type {import('i18next').i18n | null} */ 7 | let i18n = null; 8 | 9 | /** @type {(i18n: import('i18next').i18n) => void} */ 10 | let i18nResolver = () => {}; 11 | 12 | const i18Provider = new Promise((res) => { 13 | i18nResolver = res; 14 | }); 15 | 16 | /** @type {import('i18next').ThirdPartyModule} */ 17 | export const initLitI18n = { 18 | type: '3rdParty', 19 | 20 | /** 21 | * initialize the i18next instance to use 22 | * @param {import('i18next').i18n} i18nextInstance to use 23 | */ 24 | init(i18nextInstance) { 25 | setI18n(i18nextInstance); 26 | }, 27 | }; 28 | 29 | /** 30 | * Sets the i18next instance to use for the translations. 31 | * Favor using the plugin 32 | * @example 33 | * ```js 34 | * i18next.use(initLitI18n) 35 | * ``` 36 | * @param {import('i18next').i18n} i18nextInstance 37 | */ 38 | export const setI18n = (i18nextInstance) => { 39 | i18n = i18nextInstance; 40 | i18nResolver(i18n); 41 | }; 42 | 43 | /** 44 | * Used to keep track of Parts that need to be updated should the language change. 45 | * @type {Map} 46 | */ 47 | export const registry = new Map(); 48 | 49 | /** 50 | * Removes parts that are no longer connected. 51 | * Called internally on a timer but can also be called manually. 52 | */ 53 | export const registryCleanup = () => { 54 | registry.forEach((details, part) => { 55 | if (part.isConnected === false || isConnected(part) === false) { 56 | registry.delete(part); 57 | } 58 | }); 59 | }; 60 | 61 | /** lit-html does not seem to fire life cycle hook for part disconnected, we need to record and manage parts ourselves. */ 62 | setInterval(registryCleanup, 10000); 63 | 64 | let initialized = false; 65 | 66 | /** Iterates all registered translate directives re-evaluating the translations */ 67 | const updateAll = () => { 68 | registry.forEach((details, part) => { 69 | if (part.isConnected && isConnected(part)) { 70 | const translation = translateAndInit(details.keys, details.options); 71 | part.setValue(translation); 72 | } 73 | }); 74 | }; 75 | 76 | /** 77 | * Lazily sets up i18next. Incase this library is loaded before i18next has been loaded. 78 | * This defers i18next setup until the first translation is requested. 79 | * @param {string|string[]} [keys] 80 | * @param {any} [opts] 81 | * @returns {string} 82 | */ 83 | function translateAndInit(keys, opts) { 84 | if (!i18n) { 85 | return ''; 86 | } 87 | 88 | if (initialized === false) { 89 | /** Handle language changes */ 90 | i18n.on('languageChanged', updateAll); 91 | // @ts-ignore 92 | i18n.store.on('added', updateAll); 93 | initialized = true; 94 | } 95 | 96 | const translation = i18n.t(keys, opts); 97 | 98 | if (typeof translation === 'string') { 99 | return translation; 100 | } 101 | // returnObjects not supported https://www.i18next.com/translation-function/objects-and-arrays#objects 102 | return ''; 103 | } 104 | 105 | /** 106 | * @param {TranslateBase} translateDirective 107 | * @returns {boolean} 108 | */ 109 | const isConnected = (translateDirective) => { 110 | // eslint-disable-next-line prefer-destructuring 111 | const part = /** @type {import('lit-html/directive.js').Part} */ (translateDirective.part); 112 | if (part.type === PartType.ATTRIBUTE) return part.element.isConnected; 113 | if (part.type === PartType.CHILD) return part.parentNode ? part.parentNode.isConnected : false; 114 | if (part.type === PartType.PROPERTY) return part.element.isConnected; 115 | if (part.type === PartType.BOOLEAN_ATTRIBUTE) return part.element.isConnected; 116 | if (part.type === PartType.EVENT) return part.element.isConnected; 117 | if (part.type === PartType.ELEMENT) return part.element.isConnected; 118 | throw new Error('Unsupported Part'); 119 | }; 120 | 121 | /** */ 122 | class TranslateBase extends AsyncDirective { 123 | /** @abstract */ 124 | render() {} 125 | 126 | /** @param {import('lit-html/directive.js').PartInfo} part */ 127 | constructor(part) { 128 | super(part); 129 | 130 | this.value = ''; 131 | /** @type {import('lit-html/directive.js').PartInfo} */ 132 | this.part = part; 133 | } 134 | 135 | /** 136 | * @param {string | string[]} [keys] - translation key 137 | * @param {?any} [options] - i18next translation options 138 | * @returns {string|Symbol} translated string 139 | */ 140 | translate(keys, options) { 141 | let opts = options; 142 | registry.set(this, { keys, options: opts }); 143 | 144 | if (typeof options === 'function') { 145 | opts = options(); 146 | } 147 | 148 | const translation = translateAndInit(keys, opts); 149 | 150 | if (this.isConnected === false || translation === undefined || this.value === translation) { 151 | return noChange; 152 | } 153 | 154 | return translation; 155 | } 156 | 157 | /** clean up the registry */ 158 | disconnected() { 159 | registry.delete(this); 160 | } 161 | } 162 | 163 | /** */ 164 | class Translate extends TranslateBase { 165 | /** 166 | * @param {string | string[]} [keys] - translation key 167 | * @param {any} [options] - i18next translation options 168 | * @returns {string|Symbol} translated string 169 | */ 170 | render(keys, options) { 171 | i18Provider?.then(() => { 172 | this.setValue(this.translate(keys, options)); 173 | }); 174 | return noChange; 175 | } 176 | } 177 | 178 | /** */ 179 | class TranslateWhen extends TranslateBase { 180 | /** 181 | * @param {Promise} [promise] to wait for 182 | * @param {string | string[]} [keys] - translation key 183 | * @param {any} [options] - i18next translation options 184 | * @returns {string|Symbol} translated string 185 | */ 186 | render(promise, keys, options) { 187 | promise?.then(() => { 188 | this.setValue(this.translate(keys, options)); 189 | }); 190 | return noChange; 191 | } 192 | } 193 | 194 | /** 195 | * The translate directive 196 | * @example 197 | * ```js 198 | * import { translate as t, i18next, html, render } from 'lit-i18n/src/lit-i18n.js'; 199 | * i18next.init({...i18next config...}); 200 | * class MyElement extends HTMLElement { 201 | * connectedCallback() { 202 | * this.person = { name: 'Fred', age: 23, male: true }; 203 | * render(this.renderTemplate, this); 204 | * } 205 | * get renderTemplate() { 206 | * return html` 207 | * ${t('introduceself', { name: this.person.name })} 208 | *
Div with translated title
209 | *
210 | * ${t('datamodel', { person: this.person })} 211 | * 212 | * `; 213 | * } 214 | * } 215 | * ``` 216 | */ 217 | export const translate = directive(Translate); 218 | 219 | /** 220 | * @deprecated as of 4.0.0 use `translate` which already guarantees i18next is initialized 221 | * Can be used like translate but it also takes a Promise. This can be used if you can't guarantee if the i18next resource bundle is loaded. 222 | * @example 223 | * ```js 224 | * import { translateWhen } from 'lit-i18n/src/lit-i18n.js'; 225 | * const initializeI18next = i18next.use(someBackend).init(....); 226 | * const translateDirective = (keys, options) => translateWhen(initializeI18next, keys, options); 227 | * // Now you can use translateDirective in your lit-html templates. 228 | * html`
${translateDirective('some.key')}
` 229 | * ``` 230 | */ 231 | export const translateWhen = directive(TranslateWhen); 232 | --------------------------------------------------------------------------------