1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | import {FetchToken} from 'fusion-tokens';
|
11 | import {createPlugin, unescape, createToken} from 'fusion-core';
|
12 | import type {FusionPlugin, Token} from 'fusion-core';
|
13 | import {UniversalEventsToken} from 'fusion-plugin-universal-events';
|
14 |
|
15 | import type {
|
16 | I18nDepsType,
|
17 | I18nServiceType,
|
18 | TranslationsObjectType,
|
19 | } from './types.js';
|
20 |
|
21 | type LoadedTranslationsType = {
|
22 | localeCode?: string,
|
23 | translations?: TranslationsObjectType,
|
24 | };
|
25 | function loadTranslations(): LoadedTranslationsType {
|
26 | const element = document.getElementById('__TRANSLATIONS__');
|
27 | if (!element) {
|
28 | throw new Error(
|
29 | '[fusion-plugin-i18n] - Could not find a __TRANSLATIONS__ element'
|
30 | );
|
31 | }
|
32 | try {
|
33 | return JSON.parse(unescape(element.textContent));
|
34 | } catch (e) {
|
35 | throw new Error(
|
36 | '[fusion-plugin-i18n] - Error parsing __TRANSLATIONS__ element content'
|
37 | );
|
38 | }
|
39 | }
|
40 |
|
41 | type HydrationStateType = {
|
42 | localeCode?: string,
|
43 | translations: TranslationsObjectType,
|
44 | };
|
45 | export const HydrationStateToken: Token<HydrationStateType> = createToken(
|
46 | 'HydrationStateToken'
|
47 | );
|
48 |
|
49 | type PluginType = FusionPlugin<I18nDepsType, I18nServiceType>;
|
50 | const pluginFactory: () => PluginType = () =>
|
51 | createPlugin({
|
52 | deps: {
|
53 | fetch: FetchToken.optional,
|
54 | hydrationState: HydrationStateToken.optional,
|
55 | events: UniversalEventsToken.optional,
|
56 | },
|
57 | provides: ({fetch = window.fetch, hydrationState, events} = {}) => {
|
58 | class I18n {
|
59 | locale: string;
|
60 | translations: TranslationsObjectType;
|
61 | requestedKeys: Set<string>;
|
62 |
|
63 | constructor() {
|
64 | const {localeCode, translations} =
|
65 | hydrationState || loadTranslations();
|
66 | this.requestedKeys = new Set();
|
67 | this.translations = translations || {};
|
68 | if (localeCode) {
|
69 | this.locale = localeCode;
|
70 | }
|
71 | }
|
72 | async load(translationKeys) {
|
73 | const loadedKeys = Object.keys(this.translations);
|
74 | const unloaded = translationKeys.filter(key => {
|
75 | return loadedKeys.indexOf(key) < 0 && !this.requestedKeys.has(key);
|
76 | });
|
77 | if (unloaded.length > 0) {
|
78 |
|
79 |
|
80 |
|
81 | unloaded.forEach(key => {
|
82 | this.requestedKeys.add(key);
|
83 | });
|
84 | const fetchOpts = {
|
85 | method: 'POST',
|
86 | headers: {
|
87 | Accept: '*/*',
|
88 | 'Content-Type': 'application/json',
|
89 | ...(this.locale ? {'X-Fusion-Locale-Code': this.locale} : {}),
|
90 | },
|
91 | body: JSON.stringify(unloaded),
|
92 | };
|
93 |
|
94 | return fetch(
|
95 | `/_translations${
|
96 | this.locale ? `?localeCode=${this.locale}` : ''
|
97 | }`,
|
98 | fetchOpts
|
99 | )
|
100 | .then(r => {
|
101 | try {
|
102 | return r.json();
|
103 | } catch (err) {
|
104 | events && events.emit('i18n-load-error', {text: r.text()});
|
105 | throw err;
|
106 | }
|
107 | })
|
108 | .then((data: {[string]: string}) => {
|
109 | for (const key in data) {
|
110 | this.translations[key] = data[key];
|
111 | this.requestedKeys.delete(key);
|
112 | }
|
113 | })
|
114 | .catch((err: Error) => {
|
115 |
|
116 |
|
117 |
|
118 | unloaded.forEach(key => {
|
119 | this.requestedKeys.delete(key);
|
120 | });
|
121 | });
|
122 | }
|
123 | }
|
124 | translate(key, interpolations = {}) {
|
125 | const template = this.translations[key];
|
126 |
|
127 | if (typeof template !== 'string') {
|
128 | events && events.emit('i18n-translate-miss', {key});
|
129 | return key;
|
130 | }
|
131 |
|
132 | return template.replace(/\${(.*?)}/g, (_, k) =>
|
133 | interpolations[k] === void 0
|
134 | ? '${' + k + '}'
|
135 | : String(interpolations[k])
|
136 | );
|
137 | }
|
138 | }
|
139 | const i18n = new I18n();
|
140 | return {from: () => i18n};
|
141 | },
|
142 | });
|
143 |
|
144 | export default ((__BROWSER__ && pluginFactory(): any): PluginType);
|