├── 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 | [](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 |
96 | English
97 | French
98 |
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 |
--------------------------------------------------------------------------------