@rettangoli/fe 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/package.json +5 -2
- package/src/cli/build.js +22 -6
- package/src/cli/check.js +6 -1
- package/src/cli/contracts.js +32 -6
- package/src/cli/frontendEntrySource.js +54 -2
- package/src/cli/i18nBuild.js +367 -0
- package/src/cli/vitePlugin.js +91 -0
- package/src/cli/watch.js +11 -3
- package/src/core/contracts/componentFiles.js +10 -1
- package/src/core/i18n/viewReferences.js +287 -0
- package/src/core/runtime/componentOrchestrator.js +1 -0
- package/src/core/runtime/events.js +7 -0
- package/src/core/runtime/globalListeners.js +2 -0
- package/src/core/runtime/i18n.js +155 -0
- package/src/core/runtime/lifecycle.js +14 -1
- package/src/core/runtime/store.js +11 -3
- package/src/index.js +2 -0
- package/src/parser.js +1 -0
- package/src/web/createWebComponentClass.js +28 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
const EXPRESSION_PATTERN = /\$\{([^}]*)\}/g;
|
|
2
|
+
const I18N_PATH_PATTERN = /^i18n\.([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/;
|
|
3
|
+
const SIMPLE_I18N_PATH_PATTERN = /^i18n((?:\.[A-Za-z_$][\w$]*)+)$/;
|
|
4
|
+
|
|
5
|
+
const createIssue = ({ message, expression, path }) => ({
|
|
6
|
+
message,
|
|
7
|
+
expression,
|
|
8
|
+
path,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const createFindingKey = (finding) => `${finding.path}\u0000${finding.expression}`;
|
|
12
|
+
|
|
13
|
+
const extractTemplateExpressions = (text) => {
|
|
14
|
+
const expressions = [];
|
|
15
|
+
let match;
|
|
16
|
+
EXPRESSION_PATTERN.lastIndex = 0;
|
|
17
|
+
while ((match = EXPRESSION_PATTERN.exec(text)) !== null) {
|
|
18
|
+
expressions.push(match[1].trim());
|
|
19
|
+
}
|
|
20
|
+
return expressions;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const analyzeI18nExpression = ({ expression, path = "$" }) => {
|
|
24
|
+
const startsFromI18nRoot = /^i18n(?:\b|\.)/.test(expression);
|
|
25
|
+
const startsFromInternalI18nRoot = /^__rtglI18n(?:\b|\s*\()/.test(expression);
|
|
26
|
+
|
|
27
|
+
if (!startsFromI18nRoot && !startsFromInternalI18nRoot) {
|
|
28
|
+
return { references: [], issues: [] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
/^__rtglI18n\s*\(/.test(expression) ||
|
|
33
|
+
/^i18n\s*\(/.test(expression) ||
|
|
34
|
+
/^i18n(?:\.[A-Za-z_$][\w$]*)+\s*\(/.test(expression)
|
|
35
|
+
) {
|
|
36
|
+
return {
|
|
37
|
+
references: [],
|
|
38
|
+
issues: [
|
|
39
|
+
createIssue({
|
|
40
|
+
expression,
|
|
41
|
+
path,
|
|
42
|
+
message: "i18n function calls are not supported.",
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const exactPathMatch = expression.match(I18N_PATH_PATTERN);
|
|
49
|
+
if (exactPathMatch) {
|
|
50
|
+
return {
|
|
51
|
+
references: [
|
|
52
|
+
{
|
|
53
|
+
namespace: exactPathMatch[1],
|
|
54
|
+
key: exactPathMatch[2],
|
|
55
|
+
expression,
|
|
56
|
+
path,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
issues: [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const simplePathMatch = expression.match(SIMPLE_I18N_PATH_PATTERN);
|
|
64
|
+
if (simplePathMatch) {
|
|
65
|
+
const segments = simplePathMatch[1].split(".").filter(Boolean);
|
|
66
|
+
const message = segments.length < 2
|
|
67
|
+
? "i18n references must use two levels: i18n.namespace.key."
|
|
68
|
+
: "i18n references must not be deeper than i18n.namespace.key.";
|
|
69
|
+
return {
|
|
70
|
+
references: [],
|
|
71
|
+
issues: [
|
|
72
|
+
createIssue({
|
|
73
|
+
expression,
|
|
74
|
+
path,
|
|
75
|
+
message,
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (startsFromI18nRoot || startsFromInternalI18nRoot) {
|
|
82
|
+
return {
|
|
83
|
+
references: [],
|
|
84
|
+
issues: [
|
|
85
|
+
createIssue({
|
|
86
|
+
expression,
|
|
87
|
+
path,
|
|
88
|
+
message: "i18n references must be direct paths: i18n.namespace.key.",
|
|
89
|
+
}),
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { references: [], issues: [] };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const collectI18nReferencesFromView = ({
|
|
98
|
+
value,
|
|
99
|
+
path = "$",
|
|
100
|
+
references = [],
|
|
101
|
+
issues = [],
|
|
102
|
+
} = {}) => {
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
const expressions = extractTemplateExpressions(value);
|
|
105
|
+
expressions.forEach((expression) => {
|
|
106
|
+
const result = analyzeI18nExpression({ expression, path });
|
|
107
|
+
references.push(...result.references);
|
|
108
|
+
issues.push(...result.issues);
|
|
109
|
+
});
|
|
110
|
+
return { references, issues };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
value.forEach((item, index) => {
|
|
115
|
+
collectI18nReferencesFromView({
|
|
116
|
+
value: item,
|
|
117
|
+
path: `${path}[${index}]`,
|
|
118
|
+
references,
|
|
119
|
+
issues,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
return { references, issues };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (value && typeof value === "object") {
|
|
126
|
+
Object.entries(value).forEach(([key, childValue]) => {
|
|
127
|
+
collectI18nReferencesFromView({
|
|
128
|
+
value: key,
|
|
129
|
+
path: `${path}.{key}`,
|
|
130
|
+
references,
|
|
131
|
+
issues,
|
|
132
|
+
});
|
|
133
|
+
collectI18nReferencesFromView({
|
|
134
|
+
value: childValue,
|
|
135
|
+
path: `${path}.${key}`,
|
|
136
|
+
references,
|
|
137
|
+
issues,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { references, issues };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const collectEventPayloadI18nReferences = ({
|
|
146
|
+
refs,
|
|
147
|
+
references,
|
|
148
|
+
issues,
|
|
149
|
+
}) => {
|
|
150
|
+
if (!refs || typeof refs !== "object" || Array.isArray(refs)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Object.entries(refs).forEach(([refKey, refConfig]) => {
|
|
155
|
+
const eventListeners = refConfig?.eventListeners;
|
|
156
|
+
if (!eventListeners || typeof eventListeners !== "object" || Array.isArray(eventListeners)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Object.entries(eventListeners).forEach(([eventType, eventConfig]) => {
|
|
161
|
+
if (
|
|
162
|
+
!eventConfig
|
|
163
|
+
|| typeof eventConfig !== "object"
|
|
164
|
+
|| Array.isArray(eventConfig)
|
|
165
|
+
|| !Object.prototype.hasOwnProperty.call(eventConfig, "payload")
|
|
166
|
+
) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
collectI18nReferencesFromView({
|
|
171
|
+
value: eventConfig.payload,
|
|
172
|
+
path: `$.refs.${refKey}.eventListeners.${eventType}.payload`,
|
|
173
|
+
references,
|
|
174
|
+
issues,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const collectRenderableI18nReferencesFromView = ({ viewYaml } = {}) => {
|
|
181
|
+
const references = [];
|
|
182
|
+
const issues = [];
|
|
183
|
+
|
|
184
|
+
if (viewYaml && typeof viewYaml === "object" && !Array.isArray(viewYaml)) {
|
|
185
|
+
if (Object.prototype.hasOwnProperty.call(viewYaml, "template")) {
|
|
186
|
+
collectI18nReferencesFromView({
|
|
187
|
+
value: viewYaml.template,
|
|
188
|
+
path: "$.template",
|
|
189
|
+
references,
|
|
190
|
+
issues,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
collectEventPayloadI18nReferences({
|
|
195
|
+
refs: viewYaml.refs,
|
|
196
|
+
references,
|
|
197
|
+
issues,
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
collectI18nReferencesFromView({
|
|
201
|
+
value: viewYaml,
|
|
202
|
+
references,
|
|
203
|
+
issues,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { references, issues };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const validateViewI18nReferences = ({
|
|
211
|
+
viewYaml,
|
|
212
|
+
componentLabel,
|
|
213
|
+
filePath,
|
|
214
|
+
i18nContext = { enabled: false },
|
|
215
|
+
} = {}) => {
|
|
216
|
+
const supported = collectRenderableI18nReferencesFromView({
|
|
217
|
+
viewYaml,
|
|
218
|
+
});
|
|
219
|
+
const all = collectI18nReferencesFromView({
|
|
220
|
+
value: viewYaml,
|
|
221
|
+
});
|
|
222
|
+
const supportedFindingKeys = new Set([
|
|
223
|
+
...supported.references.map(createFindingKey),
|
|
224
|
+
...supported.issues.map(createFindingKey),
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
const unsupportedFindings = [
|
|
228
|
+
...all.references,
|
|
229
|
+
...all.issues,
|
|
230
|
+
].filter((finding) => !supportedFindingKeys.has(createFindingKey(finding)));
|
|
231
|
+
|
|
232
|
+
const errors = unsupportedFindings.map((finding) => ({
|
|
233
|
+
code: "RTGL-I18N-004",
|
|
234
|
+
message: `${componentLabel}: unsupported i18n expression "\${${finding.expression}}" at ${finding.path}. i18n references are supported only in template values, template bindings, and event listener payloads.`,
|
|
235
|
+
filePath,
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
supported.issues.forEach((issue) => {
|
|
239
|
+
errors.push({
|
|
240
|
+
code: "RTGL-I18N-001",
|
|
241
|
+
message: `${componentLabel}: invalid i18n expression "\${${issue.expression}}": ${issue.message}`,
|
|
242
|
+
filePath,
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const references = supported.references;
|
|
247
|
+
|
|
248
|
+
if (errors.length > 0) {
|
|
249
|
+
return errors;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!i18nContext?.enabled && references.length > 0) {
|
|
253
|
+
references.forEach((reference) => {
|
|
254
|
+
errors.push({
|
|
255
|
+
code: "RTGL-I18N-002",
|
|
256
|
+
message: `${componentLabel}: i18n reference "\${${reference.expression}}" requires fe.i18n configuration.`,
|
|
257
|
+
filePath,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
return errors;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (i18nContext?.enabled) {
|
|
264
|
+
const defaultCatalog = i18nContext.catalogs?.[i18nContext.defaultLocale] || {};
|
|
265
|
+
references.forEach((reference) => {
|
|
266
|
+
if (!Object.prototype.hasOwnProperty.call(defaultCatalog, reference.namespace)) {
|
|
267
|
+
errors.push({
|
|
268
|
+
code: "RTGL-I18N-003",
|
|
269
|
+
message: `${componentLabel}: missing i18n namespace "${reference.namespace}" in default locale "${i18nContext.defaultLocale}".`,
|
|
270
|
+
filePath,
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const namespaceMessages = defaultCatalog[reference.namespace] || {};
|
|
276
|
+
if (!Object.prototype.hasOwnProperty.call(namespaceMessages, reference.key)) {
|
|
277
|
+
errors.push({
|
|
278
|
+
code: "RTGL-I18N-003",
|
|
279
|
+
message: `${componentLabel}: missing i18n key "${reference.namespace}.${reference.key}" in default locale "${i18nContext.defaultLocale}".`,
|
|
280
|
+
filePath,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return errors;
|
|
287
|
+
};
|
|
@@ -16,6 +16,7 @@ export const createEventDispatchCallback = ({
|
|
|
16
16
|
handlers,
|
|
17
17
|
onMissingHandler,
|
|
18
18
|
parseAndRenderFn,
|
|
19
|
+
getPayloadContext,
|
|
19
20
|
}) => {
|
|
20
21
|
const getPayload = (event) => {
|
|
21
22
|
const payloadTemplate = (
|
|
@@ -28,7 +29,11 @@ export const createEventDispatchCallback = ({
|
|
|
28
29
|
if (typeof parseAndRenderFn !== "function") {
|
|
29
30
|
return payloadTemplate;
|
|
30
31
|
}
|
|
32
|
+
const payloadContext = typeof getPayloadContext === "function"
|
|
33
|
+
? getPayloadContext()
|
|
34
|
+
: {};
|
|
31
35
|
return parseAndRenderFn(payloadTemplate, {
|
|
36
|
+
...(payloadContext && typeof payloadContext === "object" ? payloadContext : {}),
|
|
32
37
|
_event: event,
|
|
33
38
|
});
|
|
34
39
|
};
|
|
@@ -162,6 +167,7 @@ export const createConfiguredEventListener = ({
|
|
|
162
167
|
stateKey,
|
|
163
168
|
fallbackCurrentTarget = null,
|
|
164
169
|
parseAndRenderFn,
|
|
170
|
+
getPayloadContext,
|
|
165
171
|
onMissingHandler,
|
|
166
172
|
nowFn = Date.now,
|
|
167
173
|
setTimeoutFn = setTimeout,
|
|
@@ -178,6 +184,7 @@ export const createConfiguredEventListener = ({
|
|
|
178
184
|
handlers,
|
|
179
185
|
onMissingHandler,
|
|
180
186
|
parseAndRenderFn,
|
|
187
|
+
getPayloadContext,
|
|
181
188
|
});
|
|
182
189
|
if (!callback) {
|
|
183
190
|
return null;
|
|
@@ -21,6 +21,7 @@ export const attachGlobalRefListeners = ({
|
|
|
21
21
|
document: globalThis.document,
|
|
22
22
|
},
|
|
23
23
|
parseAndRenderFn,
|
|
24
|
+
getPayloadContext,
|
|
24
25
|
timing = {
|
|
25
26
|
nowFn: Date.now,
|
|
26
27
|
setTimeoutFn: setTimeout,
|
|
@@ -54,6 +55,7 @@ export const attachGlobalRefListeners = ({
|
|
|
54
55
|
stateKey,
|
|
55
56
|
fallbackCurrentTarget: target,
|
|
56
57
|
parseAndRenderFn,
|
|
58
|
+
getPayloadContext,
|
|
57
59
|
nowFn: timing.nowFn,
|
|
58
60
|
setTimeoutFn: timing.setTimeoutFn,
|
|
59
61
|
clearTimeoutFn: timing.clearTimeoutFn,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const getDefaultFetch = () => {
|
|
2
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
|
|
3
|
+
return globalThis.fetch.bind(globalThis);
|
|
4
|
+
}
|
|
5
|
+
return null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const normalizeAvailableLocales = (locales = []) => [...new Set(locales)];
|
|
9
|
+
|
|
10
|
+
const createUnknownLocaleError = ({ locale, availableLocales }) => {
|
|
11
|
+
return new Error(
|
|
12
|
+
`[i18n] Unknown locale "${locale}". Available locales: ${availableLocales.join(", ")}.`,
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const createI18nRuntime = ({
|
|
17
|
+
defaultLocale,
|
|
18
|
+
fallbackLocale = defaultLocale,
|
|
19
|
+
locales = [],
|
|
20
|
+
urls = {},
|
|
21
|
+
initialCatalogs = {},
|
|
22
|
+
fetchFn = getDefaultFetch(),
|
|
23
|
+
} = {}) => {
|
|
24
|
+
const availableLocales = normalizeAvailableLocales(locales);
|
|
25
|
+
const catalogs = new Map(Object.entries(initialCatalogs || {}));
|
|
26
|
+
const listeners = new Set();
|
|
27
|
+
let currentLocale = defaultLocale || fallbackLocale || availableLocales[0] || null;
|
|
28
|
+
let localeRequestVersion = 0;
|
|
29
|
+
|
|
30
|
+
const assertKnownLocale = (locale) => {
|
|
31
|
+
if (!availableLocales.includes(locale)) {
|
|
32
|
+
throw createUnknownLocaleError({ locale, availableLocales });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getMessages = () => {
|
|
37
|
+
return (
|
|
38
|
+
catalogs.get(currentLocale) ||
|
|
39
|
+
catalogs.get(fallbackLocale) ||
|
|
40
|
+
Object.create(null)
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const notify = () => {
|
|
45
|
+
listeners.forEach((listener) => {
|
|
46
|
+
listener(currentLocale);
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const load = async (locale) => {
|
|
51
|
+
assertKnownLocale(locale);
|
|
52
|
+
|
|
53
|
+
if (catalogs.has(locale)) {
|
|
54
|
+
return catalogs.get(locale);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const url = urls[locale];
|
|
58
|
+
if (!url) {
|
|
59
|
+
throw new Error(`[i18n] Missing URL for locale "${locale}".`);
|
|
60
|
+
}
|
|
61
|
+
if (!fetchFn) {
|
|
62
|
+
throw new Error("[i18n] fetch is not available for lazy locale loading.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const response = await fetchFn(url);
|
|
66
|
+
if (!response?.ok) {
|
|
67
|
+
throw new Error(`[i18n] Failed to load locale "${locale}" from ${url}.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const catalog = await response.json();
|
|
71
|
+
catalogs.set(locale, catalog);
|
|
72
|
+
return catalog;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const activateLocale = (locale) => {
|
|
76
|
+
currentLocale = locale;
|
|
77
|
+
notify();
|
|
78
|
+
return getMessages();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const set = async (locale) => {
|
|
82
|
+
assertKnownLocale(locale);
|
|
83
|
+
const requestVersion = localeRequestVersion + 1;
|
|
84
|
+
localeRequestVersion = requestVersion;
|
|
85
|
+
const isLatestRequest = () => requestVersion === localeRequestVersion;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await load(locale);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!isLatestRequest()) {
|
|
91
|
+
return getMessages();
|
|
92
|
+
}
|
|
93
|
+
if (locale === fallbackLocale) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await load(fallbackLocale);
|
|
98
|
+
} catch (fallbackError) {
|
|
99
|
+
if (!isLatestRequest()) {
|
|
100
|
+
return getMessages();
|
|
101
|
+
}
|
|
102
|
+
throw fallbackError;
|
|
103
|
+
}
|
|
104
|
+
if (!isLatestRequest()) {
|
|
105
|
+
return getMessages();
|
|
106
|
+
}
|
|
107
|
+
return activateLocale(fallbackLocale);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!isLatestRequest()) {
|
|
111
|
+
return getMessages();
|
|
112
|
+
}
|
|
113
|
+
return activateLocale(locale);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const ready = async () => {
|
|
117
|
+
if (!currentLocale) {
|
|
118
|
+
return getMessages();
|
|
119
|
+
}
|
|
120
|
+
if (catalogs.has(currentLocale)) {
|
|
121
|
+
return getMessages();
|
|
122
|
+
}
|
|
123
|
+
return set(currentLocale);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const current = () => currentLocale;
|
|
127
|
+
const available = () => [...availableLocales];
|
|
128
|
+
const subscribe = (listener) => {
|
|
129
|
+
listeners.add(listener);
|
|
130
|
+
return () => {
|
|
131
|
+
listeners.delete(listener);
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const localeService = {
|
|
136
|
+
available,
|
|
137
|
+
current,
|
|
138
|
+
load,
|
|
139
|
+
ready,
|
|
140
|
+
set,
|
|
141
|
+
subscribe,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
available,
|
|
146
|
+
current,
|
|
147
|
+
getMessages,
|
|
148
|
+
load,
|
|
149
|
+
locale: localeService,
|
|
150
|
+
localeService,
|
|
151
|
+
ready,
|
|
152
|
+
set,
|
|
153
|
+
subscribe,
|
|
154
|
+
};
|
|
155
|
+
};
|
|
@@ -5,13 +5,26 @@ export const createRuntimeDeps = ({
|
|
|
5
5
|
store,
|
|
6
6
|
render,
|
|
7
7
|
}) => {
|
|
8
|
-
|
|
8
|
+
const runtimeDeps = {
|
|
9
9
|
...baseDeps,
|
|
10
10
|
refs,
|
|
11
11
|
dispatchEvent,
|
|
12
12
|
store,
|
|
13
13
|
render,
|
|
14
14
|
};
|
|
15
|
+
|
|
16
|
+
if (baseDeps?.__rtglI18nRuntime) {
|
|
17
|
+
Object.defineProperty(runtimeDeps, "i18n", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
get() {
|
|
21
|
+
return baseDeps.__rtglI18nRuntime.getMessages();
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
runtimeDeps.locale = baseDeps.__rtglI18nRuntime.locale;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return runtimeDeps;
|
|
15
28
|
};
|
|
16
29
|
|
|
17
30
|
export const createStoreActionDispatcher = ({
|
|
@@ -2,12 +2,20 @@ import { produce } from "immer";
|
|
|
2
2
|
|
|
3
3
|
import { isObjectPayload } from "./payload.js";
|
|
4
4
|
|
|
5
|
-
export const bindStore = (store, props, constants) => {
|
|
5
|
+
export const bindStore = (store, props, constants, runtimeContext = {}) => {
|
|
6
6
|
const { createInitialState, ...selectorsAndActions } = store;
|
|
7
7
|
const selectors = {};
|
|
8
8
|
const actions = {};
|
|
9
9
|
let currentState = {};
|
|
10
10
|
|
|
11
|
+
const createStoreContext = (state) => ({
|
|
12
|
+
state,
|
|
13
|
+
props,
|
|
14
|
+
constants,
|
|
15
|
+
i18n: runtimeContext.getI18n?.() || {},
|
|
16
|
+
locale: runtimeContext.locale,
|
|
17
|
+
});
|
|
18
|
+
|
|
11
19
|
if (createInitialState) {
|
|
12
20
|
currentState = createInitialState({ props, constants });
|
|
13
21
|
}
|
|
@@ -15,7 +23,7 @@ export const bindStore = (store, props, constants) => {
|
|
|
15
23
|
Object.entries(selectorsAndActions).forEach(([key, fn]) => {
|
|
16
24
|
if (key.startsWith("select")) {
|
|
17
25
|
selectors[key] = (...args) => {
|
|
18
|
-
return fn(
|
|
26
|
+
return fn(createStoreContext(currentState), ...args);
|
|
19
27
|
};
|
|
20
28
|
return;
|
|
21
29
|
}
|
|
@@ -28,7 +36,7 @@ export const bindStore = (store, props, constants) => {
|
|
|
28
36
|
);
|
|
29
37
|
}
|
|
30
38
|
currentState = produce(currentState, (draft) => {
|
|
31
|
-
return fn(
|
|
39
|
+
return fn(createStoreContext(draft), normalizedPayload);
|
|
32
40
|
});
|
|
33
41
|
return currentState;
|
|
34
42
|
};
|
package/src/index.js
CHANGED
package/src/parser.js
CHANGED
|
@@ -227,6 +227,7 @@ export const createVirtualDom = ({
|
|
|
227
227
|
eventRateLimitState,
|
|
228
228
|
stateKey,
|
|
229
229
|
parseAndRenderFn: jemplParseAndRender,
|
|
230
|
+
getPayloadContext: () => viewData,
|
|
230
231
|
onMissingHandler: (missingHandlerName) => {
|
|
231
232
|
console.warn(
|
|
232
233
|
`[Parser] Handler '${missingHandlerName}' for refKey '${bestMatchRefKey}' (matching '${matchIdentity}') is referenced but not found in available handlers.`,
|
|
@@ -52,6 +52,8 @@ export const createWebComponentClass = ({
|
|
|
52
52
|
_globalListenersCleanup;
|
|
53
53
|
_oldVNode;
|
|
54
54
|
deps;
|
|
55
|
+
i18nRuntime;
|
|
56
|
+
_i18nUnsubscribe;
|
|
55
57
|
_propsSchemaKeys = [];
|
|
56
58
|
cssText;
|
|
57
59
|
|
|
@@ -64,7 +66,10 @@ export const createWebComponentClass = ({
|
|
|
64
66
|
if (this.store.selectViewData) {
|
|
65
67
|
data = this.store.selectViewData();
|
|
66
68
|
}
|
|
67
|
-
return
|
|
69
|
+
return {
|
|
70
|
+
...data,
|
|
71
|
+
i18n: this.i18nRuntime?.getMessages?.() || {},
|
|
72
|
+
};
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
connectedCallback() {
|
|
@@ -74,6 +79,11 @@ export const createWebComponentClass = ({
|
|
|
74
79
|
});
|
|
75
80
|
this.shadow = dom.shadow;
|
|
76
81
|
this.renderTarget = dom.renderTarget;
|
|
82
|
+
if (this.i18nRuntime?.subscribe && !this._i18nUnsubscribe) {
|
|
83
|
+
this._i18nUnsubscribe = this.i18nRuntime.subscribe(() => {
|
|
84
|
+
this.render();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
77
87
|
runConnectedComponentLifecycle({
|
|
78
88
|
instance: this,
|
|
79
89
|
parseAndRenderFn: parseAndRender,
|
|
@@ -82,6 +92,10 @@ export const createWebComponentClass = ({
|
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
disconnectedCallback() {
|
|
95
|
+
if (this._i18nUnsubscribe) {
|
|
96
|
+
this._i18nUnsubscribe();
|
|
97
|
+
this._i18nUnsubscribe = undefined;
|
|
98
|
+
}
|
|
85
99
|
runDisconnectedComponentLifecycle({
|
|
86
100
|
instance: this,
|
|
87
101
|
clearTimerFn: clearTimeout,
|
|
@@ -140,7 +154,11 @@ export const createWebComponentClass = ({
|
|
|
140
154
|
this._propsSchemaKeys = propsSchemaKeys;
|
|
141
155
|
this.elementName = elementName;
|
|
142
156
|
this.styles = styles;
|
|
143
|
-
this.
|
|
157
|
+
this.i18nRuntime = deps?.__rtglI18nRuntime;
|
|
158
|
+
this.store = bindStore(store, this.props, this.constants, {
|
|
159
|
+
getI18n: () => this.i18nRuntime?.getMessages?.() || {},
|
|
160
|
+
locale: this.i18nRuntime?.locale,
|
|
161
|
+
});
|
|
144
162
|
this.template = template;
|
|
145
163
|
this.handlers = handlers;
|
|
146
164
|
this.methods = methods;
|
|
@@ -148,12 +166,20 @@ export const createWebComponentClass = ({
|
|
|
148
166
|
this.patch = patch;
|
|
149
167
|
this.deps = {
|
|
150
168
|
...deps,
|
|
169
|
+
locale: this.i18nRuntime?.locale || deps?.locale,
|
|
151
170
|
store: this.store,
|
|
152
171
|
render: this.render,
|
|
153
172
|
handlers,
|
|
154
173
|
props: this.props,
|
|
155
174
|
constants: this.constants,
|
|
156
175
|
};
|
|
176
|
+
if (this.i18nRuntime) {
|
|
177
|
+
Object.defineProperty(this.deps, "i18n", {
|
|
178
|
+
enumerable: true,
|
|
179
|
+
configurable: true,
|
|
180
|
+
get: () => this.i18nRuntime.getMessages(),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
157
183
|
bindMethods(this, this.methods);
|
|
158
184
|
// Keep the Snabbdom helper off public prop names (e.g. schema prop "h").
|
|
159
185
|
this._snabbdomH = h;
|