@secorto/i18n 0.0.4 → 0.1.0
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/LICENSE +8 -0
- package/dist/index.d.mts +31 -4
- package/dist/index.mjs +50 -17
- package/package.json +9 -9
- package/src/section/detail-path.ts +15 -5
- package/src/section/details-links.ts +5 -4
- package/src/section/entry-adapter.ts +3 -2
- package/src/section/routes.ts +10 -3
- package/src/section/translation-index.ts +14 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Sergio Orozco Torres
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/index.d.mts
CHANGED
|
@@ -77,6 +77,7 @@ declare function isMissing<L extends string>(link: TranslationLink<L>): link is
|
|
|
77
77
|
* @throws {Error} If `links` is empty or if no accessible link exists.
|
|
78
78
|
*/
|
|
79
79
|
declare function resolveDefaultAccessibleLink<L extends string>(links: TranslationLink<L>[], defaultLang: L): AccessibleTranslationLink<L>;
|
|
80
|
+
declare function createAvailableLinks<L extends string>(locales: Locales<L>, getHref: (locale: L) => string): AvailableLink<L>[];
|
|
80
81
|
//#endregion
|
|
81
82
|
//#region src/core/standalone.d.ts
|
|
82
83
|
/**
|
|
@@ -145,7 +146,7 @@ interface SectionRoutes<Section extends string, Language extends string> {
|
|
|
145
146
|
/**
|
|
146
147
|
* Returns the configured section identifiers.
|
|
147
148
|
*/
|
|
148
|
-
getSections(): Section[];
|
|
149
|
+
getSections(): readonly Section[];
|
|
149
150
|
/**
|
|
150
151
|
* Returns the localized slug for a section.
|
|
151
152
|
*
|
|
@@ -171,6 +172,15 @@ interface SectionRoutes<Section extends string, Language extends string> {
|
|
|
171
172
|
* @returns Full URL for the entry.
|
|
172
173
|
*/
|
|
173
174
|
getEntryURL(section: Section, locale: Language, slug: string): string;
|
|
175
|
+
/**
|
|
176
|
+
* Returns the localized URL for a tag page inside a section.
|
|
177
|
+
*
|
|
178
|
+
* @param section Section identifier.
|
|
179
|
+
* @param locale Locale identifier.
|
|
180
|
+
* @param tag Tag name.
|
|
181
|
+
* @returns Full URL for the tag page.
|
|
182
|
+
*/
|
|
183
|
+
getEntryTagURL(section: Section, locale: Language, tag: string): string;
|
|
174
184
|
}
|
|
175
185
|
/**
|
|
176
186
|
* Constructs a nominal SectionRoutes value from a raw SectionDictionary.
|
|
@@ -209,11 +219,16 @@ interface LocalizedEntry<TEntry, L extends string> {
|
|
|
209
219
|
* @template E - The type of the content (e.g., { title: string, body: string }).
|
|
210
220
|
* @template C - The section of the application (e.g., 'blog', 'docs').
|
|
211
221
|
*/
|
|
212
|
-
type
|
|
222
|
+
type TranslationGroup<L extends string, TEntry> = Partial<Record<L, LocalizedEntry<TEntry, L>>>;
|
|
223
|
+
type TranslationIndex<L extends string, TEntry> = Partial<Record<string, TranslationGroup<L, TEntry>>>;
|
|
213
224
|
/**
|
|
214
225
|
* Builds a translation index from an array of localized entries.
|
|
215
226
|
* The index groups entries by their translation key and locale.
|
|
216
227
|
* If duplicate entries for the same translation key and locale are found, an error is thrown.
|
|
228
|
+
*
|
|
229
|
+
* Invariant: a key is only inserted into the map after at least one locale
|
|
230
|
+
* has been assigned to it. An empty group is therefore an invalid state.
|
|
231
|
+
*
|
|
217
232
|
* @template E - The type of the content (e.g., { title: string, body: string }).
|
|
218
233
|
* @template C - The section of the application (e.g., 'blog', 'docs').
|
|
219
234
|
* @template L - The language code (e.g., 'es', 'en').
|
|
@@ -252,7 +267,7 @@ type DetailPath<C extends string, L extends string, TEntry extends GenericCollec
|
|
|
252
267
|
props: {
|
|
253
268
|
entry: LocalizedEntry<TEntry, L>;
|
|
254
269
|
section: C;
|
|
255
|
-
siblings: TranslationIndex<L, TEntry>[string]
|
|
270
|
+
siblings: NonNullable<TranslationIndex<L, TEntry>[string]>;
|
|
256
271
|
};
|
|
257
272
|
};
|
|
258
273
|
/**
|
|
@@ -296,4 +311,16 @@ declare function getStaticPathsEntries<C extends string, E extends object, L ext
|
|
|
296
311
|
*/
|
|
297
312
|
declare function createDetailTranslationLinks<C extends string, L extends string, TEntry extends GenericCollectionEntry<C, object>>(siblings: Partial<Record<L, LocalizedEntry<TEntry, L>>>, sectionRoutes: SectionRoutes<C, L>, locales: Locales<L>): TranslationLink<L>[];
|
|
298
313
|
//#endregion
|
|
299
|
-
|
|
314
|
+
//#region src/section/list-paths.d.ts
|
|
315
|
+
interface SectionPath<TLocale extends string, TSection extends string> {
|
|
316
|
+
params: {
|
|
317
|
+
locale: TLocale;
|
|
318
|
+
section: string;
|
|
319
|
+
};
|
|
320
|
+
props: {
|
|
321
|
+
section: TSection;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
declare function getStaticPathsSections<C extends string, L extends string>(routes: SectionRoutes<C, L>, allowedLocales: Locales<L>): Promise<SectionPath<L, C>[]>;
|
|
325
|
+
//#endregion
|
|
326
|
+
export { AccessibleTranslationLink, AvailableLink, DetailPath, DraftLink, GenericCollectionEntry, Locales, LocalizedEntry, MissingLink, SectionDictionary, SectionPath, SectionRoutes, StandalonePageEntry, StandalonePageIndex, TranslationGroup, TranslationIndex, TranslationLink, adaptToLocalizedEntry, availableLink, createAvailableLinks, createDetailTranslationLinks, createLocales, createSectionRoutes, createStandalonePageLinks, createTranslationIndex, draftLink, extractCleanId, getStaticPathsEntries, getStaticPathsSections, isAccessible, isAvailable, isDraft, isMissing, missingLink, resolveDefaultAccessibleLink, resolveTranslationKey };
|
package/dist/index.mjs
CHANGED
|
@@ -90,6 +90,13 @@ function resolveDefaultAccessibleLink(links, defaultLang) {
|
|
|
90
90
|
if (firstDraft) return firstDraft;
|
|
91
91
|
throw new Error("resolveDefaultAccessibleLink: expected at least one accessible link");
|
|
92
92
|
}
|
|
93
|
+
function createAvailableLinks(locales, getHref) {
|
|
94
|
+
return locales.all.map((locale) => ({
|
|
95
|
+
type: "available",
|
|
96
|
+
href: getHref(locale),
|
|
97
|
+
locale
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
93
100
|
//#endregion
|
|
94
101
|
//#region src/core/extract-id.ts
|
|
95
102
|
/**
|
|
@@ -155,7 +162,8 @@ function createStandalonePageLinks(path, translationKey, index, locales) {
|
|
|
155
162
|
*/
|
|
156
163
|
function createSectionRoutes(routes) {
|
|
157
164
|
const seen = /* @__PURE__ */ new Map();
|
|
158
|
-
|
|
165
|
+
const sections = Object.freeze(Object.keys(routes));
|
|
166
|
+
for (const section of sections) {
|
|
159
167
|
const localized = routes[section];
|
|
160
168
|
for (const locale of Object.keys(localized)) {
|
|
161
169
|
const slug = localized[locale];
|
|
@@ -167,16 +175,20 @@ function createSectionRoutes(routes) {
|
|
|
167
175
|
seen.set(key, section);
|
|
168
176
|
}
|
|
169
177
|
}
|
|
170
|
-
const
|
|
178
|
+
for (const section of sections) Object.freeze(routes[section]);
|
|
179
|
+
Object.freeze(routes);
|
|
180
|
+
const getSections = () => sections;
|
|
171
181
|
const getSectionRoute = (section, locale) => routes[section][locale];
|
|
172
182
|
const getSectionURL = (section, locale) => `/${locale}/${getSectionRoute(section, locale)}`;
|
|
183
|
+
const getEntryTagURL = (section, locale, tag) => `${getSectionURL(section, locale)}/tags/${encodeURIComponent(tag)}`;
|
|
173
184
|
const getEntryURL = (section, locale, slug) => `${getSectionURL(section, locale)}/${slug}`;
|
|
174
185
|
return {
|
|
175
186
|
routes,
|
|
176
187
|
getSections,
|
|
177
188
|
getSectionRoute,
|
|
178
189
|
getSectionURL,
|
|
179
|
-
getEntryURL
|
|
190
|
+
getEntryURL,
|
|
191
|
+
getEntryTagURL
|
|
180
192
|
};
|
|
181
193
|
}
|
|
182
194
|
//#endregion
|
|
@@ -185,6 +197,10 @@ function createSectionRoutes(routes) {
|
|
|
185
197
|
* Builds a translation index from an array of localized entries.
|
|
186
198
|
* The index groups entries by their translation key and locale.
|
|
187
199
|
* If duplicate entries for the same translation key and locale are found, an error is thrown.
|
|
200
|
+
*
|
|
201
|
+
* Invariant: a key is only inserted into the map after at least one locale
|
|
202
|
+
* has been assigned to it. An empty group is therefore an invalid state.
|
|
203
|
+
*
|
|
188
204
|
* @template E - The type of the content (e.g., { title: string, body: string }).
|
|
189
205
|
* @template C - The section of the application (e.g., 'blog', 'docs').
|
|
190
206
|
* @template L - The language code (e.g., 'es', 'en').
|
|
@@ -199,7 +215,7 @@ function createTranslationIndex(entries) {
|
|
|
199
215
|
const locale = entry.locale;
|
|
200
216
|
let group = map.get(key);
|
|
201
217
|
if (!group) {
|
|
202
|
-
group =
|
|
218
|
+
group = Object.create(null);
|
|
203
219
|
map.set(key, group);
|
|
204
220
|
} else if (locale in group) throw new Error(`Duplicate translation for key "${key}" and locale "${locale}"`);
|
|
205
221
|
group[locale] = entry;
|
|
@@ -263,18 +279,22 @@ async function getStaticPathsEntries(routes, fetchCollection, allowedLocales) {
|
|
|
263
279
|
for (const sectionKey of routes.getSections()) {
|
|
264
280
|
const localizedEntries = (await fetchCollection(sectionKey)).map((entry) => adaptToLocalizedEntry(entry, allowedLocales));
|
|
265
281
|
const index = createTranslationIndex(localizedEntries);
|
|
266
|
-
for (const localized of localizedEntries)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
for (const localized of localizedEntries) {
|
|
283
|
+
const siblings = index[localized.translationKey];
|
|
284
|
+
if (!siblings || Object.keys(siblings).length === 0) throw new Error(`Missing translation group for key "${localized.translationKey}"`);
|
|
285
|
+
allPaths.push({
|
|
286
|
+
params: {
|
|
287
|
+
locale: localized.locale,
|
|
288
|
+
section: routes.getSectionRoute(sectionKey, localized.locale),
|
|
289
|
+
id: localized.cleanId
|
|
290
|
+
},
|
|
291
|
+
props: {
|
|
292
|
+
entry: localized,
|
|
293
|
+
section: sectionKey,
|
|
294
|
+
siblings
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
278
298
|
}
|
|
279
299
|
return allPaths;
|
|
280
300
|
}
|
|
@@ -301,4 +321,17 @@ function createDetailTranslationLinks(siblings, sectionRoutes, locales) {
|
|
|
301
321
|
});
|
|
302
322
|
}
|
|
303
323
|
//#endregion
|
|
304
|
-
|
|
324
|
+
//#region src/section/list-paths.ts
|
|
325
|
+
async function getStaticPathsSections(routes, allowedLocales) {
|
|
326
|
+
const allPaths = [];
|
|
327
|
+
for (const sectionKey of routes.getSections()) for (const locale of allowedLocales.all) allPaths.push({
|
|
328
|
+
params: {
|
|
329
|
+
locale,
|
|
330
|
+
section: routes.getSectionRoute(sectionKey, locale)
|
|
331
|
+
},
|
|
332
|
+
props: { section: sectionKey }
|
|
333
|
+
});
|
|
334
|
+
return allPaths;
|
|
335
|
+
}
|
|
336
|
+
//#endregion
|
|
337
|
+
export { adaptToLocalizedEntry, availableLink, createAvailableLinks, createDetailTranslationLinks, createLocales, createSectionRoutes, createStandalonePageLinks, createTranslationIndex, draftLink, extractCleanId, getStaticPathsEntries, getStaticPathsSections, isAccessible, isAvailable, isDraft, isMissing, missingLink, resolveDefaultAccessibleLink, resolveTranslationKey };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secorto/i18n",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Framework-agnostic i18n primitive",
|
|
@@ -41,13 +41,6 @@
|
|
|
41
41
|
"dist",
|
|
42
42
|
"src"
|
|
43
43
|
],
|
|
44
|
-
"scripts": {
|
|
45
|
-
"build": "tsdown",
|
|
46
|
-
"clean": "rimraf dist",
|
|
47
|
-
"test": "vitest --run",
|
|
48
|
-
"test:unit": "vitest",
|
|
49
|
-
"test:unit:watch": "vitest --watch"
|
|
50
|
-
},
|
|
51
44
|
"devDependencies": {
|
|
52
45
|
"vitest": "^4.1.10"
|
|
53
46
|
},
|
|
@@ -55,5 +48,12 @@
|
|
|
55
48
|
"entry": [
|
|
56
49
|
"src/index.ts"
|
|
57
50
|
]
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsdown",
|
|
54
|
+
"clean": "rimraf dist",
|
|
55
|
+
"test": "vitest --run",
|
|
56
|
+
"test:unit": "vitest",
|
|
57
|
+
"test:unit:watch": "vitest --watch"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { Locales } from '../core'
|
|
1
|
+
import type { Locales } from '../core'
|
|
2
2
|
import {
|
|
3
3
|
createTranslationIndex,
|
|
4
|
+
} from './translation-index'
|
|
5
|
+
import type {
|
|
4
6
|
LocalizedEntry,
|
|
5
7
|
TranslationIndex,
|
|
6
8
|
} from './translation-index'
|
|
7
9
|
import {
|
|
8
10
|
adaptToLocalizedEntry,
|
|
9
|
-
GenericCollectionEntry,
|
|
10
11
|
} from './entry-adapter'
|
|
11
|
-
import {
|
|
12
|
+
import type { GenericCollectionEntry } from './entry-adapter'
|
|
13
|
+
import type { SectionRoutes } from './routes'
|
|
12
14
|
|
|
13
15
|
export type DetailPath<
|
|
14
16
|
C extends string,
|
|
@@ -23,7 +25,7 @@ export type DetailPath<
|
|
|
23
25
|
props: {
|
|
24
26
|
entry: LocalizedEntry<TEntry, L>
|
|
25
27
|
section: C
|
|
26
|
-
siblings: TranslationIndex<L, TEntry>[string]
|
|
28
|
+
siblings: NonNullable<TranslationIndex<L, TEntry>[string]>
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -76,6 +78,14 @@ export async function getStaticPathsEntries<
|
|
|
76
78
|
const index = createTranslationIndex(localizedEntries)
|
|
77
79
|
|
|
78
80
|
for (const localized of localizedEntries) {
|
|
81
|
+
const siblings = index[localized.translationKey]
|
|
82
|
+
|
|
83
|
+
if (!siblings || Object.keys(siblings).length === 0) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Missing translation group for key "${localized.translationKey}"`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
allPaths.push({
|
|
80
90
|
params: {
|
|
81
91
|
locale: localized.locale,
|
|
@@ -85,7 +95,7 @@ export async function getStaticPathsEntries<
|
|
|
85
95
|
props: {
|
|
86
96
|
entry: localized,
|
|
87
97
|
section: sectionKey,
|
|
88
|
-
siblings
|
|
98
|
+
siblings,
|
|
89
99
|
},
|
|
90
100
|
})
|
|
91
101
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { availableLink, draftLink,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { availableLink, draftLink, missingLink } from '../core'
|
|
2
|
+
import type { Locales, TranslationLink } from '../core'
|
|
3
|
+
import type { GenericCollectionEntry } from './entry-adapter'
|
|
4
|
+
import type { SectionRoutes } from './routes'
|
|
5
|
+
import type { LocalizedEntry } from './translation-index'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Creates translation links for all supported locales.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { extractCleanId
|
|
2
|
-
import {
|
|
1
|
+
import { extractCleanId } from '../core'
|
|
2
|
+
import type { Locales } from '../core'
|
|
3
|
+
import type { LocalizedEntry } from './translation-index'
|
|
3
4
|
|
|
4
5
|
export interface GenericCollectionEntry<
|
|
5
6
|
C extends string,
|
package/src/section/routes.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface SectionRoutes<
|
|
|
31
31
|
/**
|
|
32
32
|
* Returns the configured section identifiers.
|
|
33
33
|
*/
|
|
34
|
-
getSections(): Section[]
|
|
34
|
+
getSections(): readonly Section[]
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Returns the localized slug for a section.
|
|
@@ -84,8 +84,9 @@ export function createSectionRoutes<
|
|
|
84
84
|
routes: SectionDictionary<Section, Language, string>
|
|
85
85
|
): SectionRoutes<Section, Language> {
|
|
86
86
|
const seen = new Map<string, Section>()
|
|
87
|
+
const sections = Object.freeze(Object.keys(routes) as Section[])
|
|
87
88
|
|
|
88
|
-
for (const section of
|
|
89
|
+
for (const section of sections) {
|
|
89
90
|
const localized = routes[section]
|
|
90
91
|
|
|
91
92
|
for (const locale of Object.keys(localized) as Language[]) {
|
|
@@ -103,7 +104,13 @@ export function createSectionRoutes<
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
// Enforce runtime immutability for the value object invariants.
|
|
108
|
+
for (const section of sections) {
|
|
109
|
+
Object.freeze(routes[section])
|
|
110
|
+
}
|
|
111
|
+
Object.freeze(routes)
|
|
112
|
+
|
|
113
|
+
const getSections = (): readonly Section[] => sections
|
|
107
114
|
|
|
108
115
|
const getSectionRoute = (section: Section, locale: Language): string =>
|
|
109
116
|
routes[section][locale]
|
|
@@ -21,18 +21,24 @@ export interface LocalizedEntry<
|
|
|
21
21
|
* @template E - The type of the content (e.g., { title: string, body: string }).
|
|
22
22
|
* @template C - The section of the application (e.g., 'blog', 'docs').
|
|
23
23
|
*/
|
|
24
|
+
export type TranslationGroup<
|
|
25
|
+
L extends string,
|
|
26
|
+
TEntry,
|
|
27
|
+
> = Partial<Record<L, LocalizedEntry<TEntry, L>>>
|
|
28
|
+
|
|
24
29
|
export type TranslationIndex<
|
|
25
30
|
L extends string,
|
|
26
31
|
TEntry
|
|
27
|
-
> = Record<
|
|
28
|
-
string,
|
|
29
|
-
Partial<Record<L, LocalizedEntry<TEntry, L>>>
|
|
30
|
-
>
|
|
32
|
+
> = Partial<Record<string, TranslationGroup<L, TEntry>>>
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Builds a translation index from an array of localized entries.
|
|
34
36
|
* The index groups entries by their translation key and locale.
|
|
35
37
|
* If duplicate entries for the same translation key and locale are found, an error is thrown.
|
|
38
|
+
*
|
|
39
|
+
* Invariant: a key is only inserted into the map after at least one locale
|
|
40
|
+
* has been assigned to it. An empty group is therefore an invalid state.
|
|
41
|
+
*
|
|
36
42
|
* @template E - The type of the content (e.g., { title: string, body: string }).
|
|
37
43
|
* @template C - The section of the application (e.g., 'blog', 'docs').
|
|
38
44
|
* @template L - The language code (e.g., 'es', 'en').
|
|
@@ -46,17 +52,16 @@ export function createTranslationIndex<
|
|
|
46
52
|
>(
|
|
47
53
|
entries: readonly LocalizedEntry<TEntry, L>[]
|
|
48
54
|
): TranslationIndex<L, TEntry> {
|
|
49
|
-
// Using map to safely mutate internally without lying to TypeScript
|
|
50
|
-
const map = new Map<string,
|
|
55
|
+
// Using map to safely mutate internally without lying to TypeScript.
|
|
56
|
+
const map = new Map<string, TranslationGroup<L, TEntry>>()
|
|
51
57
|
|
|
52
58
|
for (const entry of entries) {
|
|
53
59
|
const key = entry.translationKey
|
|
54
60
|
const locale = entry.locale
|
|
55
61
|
|
|
56
|
-
// Get or create the group for this key
|
|
57
62
|
let group = map.get(key)
|
|
58
63
|
if (!group) {
|
|
59
|
-
group =
|
|
64
|
+
group = Object.create(null) as TranslationGroup<L, TEntry>
|
|
60
65
|
map.set(key, group)
|
|
61
66
|
} else if (locale in group) {
|
|
62
67
|
throw new Error(
|
|
@@ -64,8 +69,8 @@ export function createTranslationIndex<
|
|
|
64
69
|
)
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
// Assign directly to the entry
|
|
68
72
|
group[locale] = entry
|
|
69
73
|
}
|
|
74
|
+
|
|
70
75
|
return Object.fromEntries(map)
|
|
71
76
|
}
|