@douglasneuroinformatics/libui 4.4.0 → 4.5.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.
Files changed (92) hide show
  1. package/dist/chunk-5B62SIBQ.js +60 -0
  2. package/dist/chunk-5B62SIBQ.js.map +1 -0
  3. package/dist/chunk-GT5NL2RJ.js +306 -0
  4. package/dist/chunk-GT5NL2RJ.js.map +1 -0
  5. package/dist/{chunk-LSGD4EQY.js → chunk-SGSRBDMT.js} +30 -24
  6. package/dist/{chunk-LSGD4EQY.js.map → chunk-SGSRBDMT.js.map} +1 -1
  7. package/dist/components.d.ts +62 -46
  8. package/dist/components.js +955 -652
  9. package/dist/components.js.map +1 -1
  10. package/dist/hooks.d.ts +3 -6
  11. package/dist/hooks.js +3 -2
  12. package/dist/i18n.d.ts +25 -28
  13. package/dist/i18n.js +4 -5
  14. package/dist/{types-DDyMlEuL.d.ts → types-CxoDu4Em.d.ts} +18 -6
  15. package/dist/utils.js +1 -0
  16. package/package.json +9 -15
  17. package/src/components/Accordion/Accordion.stories.tsx +1 -1
  18. package/src/components/ActionDropdown/ActionDropdown.stories.tsx +1 -1
  19. package/src/components/AlertDialog/AlertDialog.stories.tsx +1 -1
  20. package/src/components/ArrowToggle/ArrowToggle.stories.tsx +1 -1
  21. package/src/components/Avatar/Avatar.stories.tsx +1 -1
  22. package/src/components/Badge/Badge.stories.tsx +1 -1
  23. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  24. package/src/components/Button/Button.stories.tsx +1 -1
  25. package/src/components/Card/Card.stories.tsx +1 -1
  26. package/src/components/Chart/Chart.stories.tsx +1 -1
  27. package/src/components/Checkbox/Checkbox.stories.tsx +2 -2
  28. package/src/components/ClientTable/ClientTable.stories.tsx +1 -1
  29. package/src/components/Collapsible/Collapsible.stories.tsx +1 -1
  30. package/src/components/Command/Command.stories.tsx +1 -1
  31. package/src/components/ContextMenu/ContextMenu.stories.tsx +1 -1
  32. package/src/components/CopyButton/CopyButton.stories.tsx +1 -1
  33. package/src/components/DataTable/DataTable.stories.tsx +15 -15
  34. package/src/components/DataTable/DataTable.tsx +24 -23
  35. package/src/components/DataTable/DestructiveActionDialog.tsx +2 -6
  36. package/src/components/DatePicker/DatePicker.stories.tsx +1 -1
  37. package/src/components/Dialog/Dialog.stories.tsx +1 -1
  38. package/src/components/Drawer/Drawer.stories.tsx +1 -1
  39. package/src/components/DropdownButton/DropdownButton.stories.tsx +1 -1
  40. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +1 -1
  41. package/src/components/ErrorFallback/ErrorFallback.stories.tsx +1 -1
  42. package/src/components/FileDropzone/FileDropzone.stories.tsx +1 -1
  43. package/src/components/Form/BooleanField/BooleanField.stories.tsx +1 -1
  44. package/src/components/Form/DateField/DateField.stories.tsx +1 -1
  45. package/src/components/Form/Form.stories.tsx +2 -3
  46. package/src/components/Form/Form.tsx +17 -9
  47. package/src/components/Form/NumberField/NumberField.stories.tsx +1 -1
  48. package/src/components/Form/SetField/SetField.stories.tsx +1 -1
  49. package/src/components/Form/StringField/StringField.stories.tsx +1 -1
  50. package/src/components/Form/types.ts +0 -33
  51. package/src/components/Heading/Heading.stories.tsx +1 -1
  52. package/src/components/HoverCard/HoverCard.stories.tsx +2 -2
  53. package/src/components/Input/Input.stories.tsx +1 -1
  54. package/src/components/Label/Label.stories.tsx +1 -1
  55. package/src/components/LanguageToggle/LanguageToggle.stories.tsx +22 -2
  56. package/src/components/LineGraph/LineGraph.stories.tsx +1 -1
  57. package/src/components/ListboxDropdown/ListboxDropdown.stories.tsx +1 -1
  58. package/src/components/MenuBar/MenuBar.stories.tsx +1 -1
  59. package/src/components/NotificationHub/NotificationHub.stories.tsx +1 -1
  60. package/src/components/OneTimePasswordInput/OneTimePasswordInput.stories.tsx +1 -1
  61. package/src/components/Pagination/Pagination.stories.tsx +1 -1
  62. package/src/components/Popover/Popover.stories.tsx +4 -4
  63. package/src/components/Progress/Progress.stories.tsx +1 -1
  64. package/src/components/RadioGroup/RadioGroup.stories.tsx +1 -1
  65. package/src/components/Resizable/Resizable.stories.tsx +1 -1
  66. package/src/components/ScrollArea/ScrollArea.stories.tsx +1 -1
  67. package/src/components/SearchBar/SearchBar.stories.tsx +1 -1
  68. package/src/components/Select/Select.stories.tsx +1 -1
  69. package/src/components/Separator/Separator.stories.tsx +3 -3
  70. package/src/components/Sheet/Sheet.stories.tsx +1 -1
  71. package/src/components/Slider/Slider.stories.tsx +1 -1
  72. package/src/components/Spinner/Spinner.stories.tsx +1 -1
  73. package/src/components/SpinnerIcon/SpinnerIcon.stories.tsx +1 -1
  74. package/src/components/StatisticCard/StatisticCard.stories.tsx +1 -1
  75. package/src/components/Switch/Switch.stories.tsx +1 -1
  76. package/src/components/Table/Table.stories.tsx +1 -1
  77. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  78. package/src/components/TextArea/TextArea.stories.tsx +1 -1
  79. package/src/components/ThemeToggle/ThemeToggle.stories.tsx +1 -1
  80. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  81. package/src/components/index.ts +1 -0
  82. package/src/hooks/useTranslation/useTranslation.ts +39 -24
  83. package/src/i18n/__tests__/translator.test.ts +91 -0
  84. package/src/i18n/index.ts +4 -1
  85. package/src/i18n/translator.ts +129 -0
  86. package/src/i18n/types.ts +23 -6
  87. package/src/testing/mocks.ts +0 -9
  88. package/src/testing/setup-tests.ts +3 -2
  89. package/dist/chunk-VFVO337W.js +0 -252
  90. package/dist/chunk-VFVO337W.js.map +0 -1
  91. package/src/i18n/internal.ts +0 -23
  92. package/src/i18n/store.ts +0 -69
@@ -0,0 +1,129 @@
1
+ import { format } from '@douglasneuroinformatics/libjs';
2
+ import { get } from 'lodash-es';
3
+ import type { SetOptional } from 'type-fest';
4
+
5
+ import libui from './translations/libui.json';
6
+
7
+ import type {
8
+ Language,
9
+ TranslateFormatArgs,
10
+ TranslateOptions,
11
+ TranslationKey,
12
+ Translations,
13
+ TranslatorType
14
+ } from './types';
15
+
16
+ type TranslatorEventMap = {
17
+ languageChange: (...args: [language: Language]) => void;
18
+ };
19
+
20
+ type TranslatorConfig = {
21
+ defaultLanguage?: Language;
22
+ translations: SetOptional<Translations, 'libui'>;
23
+ };
24
+
25
+ function InitializedOnly<T extends Translator, TArgs extends any[], TReturn>(
26
+ target: (this: T, ...args: TArgs) => TReturn,
27
+ context: ClassGetterDecoratorContext<T> | ClassMethodDecoratorContext<T> | ClassSetterDecoratorContext<T>
28
+ ) {
29
+ const name = context.name.toString();
30
+ function replacementMethod(this: T, ...args: TArgs): TReturn {
31
+ if (!this.isInitialized) {
32
+ throw new Error(`Cannot access ${context.kind} '${name}' of Translator instance before initialization`);
33
+ }
34
+ return target.call(this, ...args);
35
+ }
36
+ return replacementMethod;
37
+ }
38
+
39
+ export class Translator implements TranslatorType<TranslationKey> {
40
+ #config: Required<TranslatorConfig>;
41
+ #eventHandlers: {
42
+ [K in keyof TranslatorEventMap]: Set<TranslatorEventMap[K]>;
43
+ };
44
+ #resolvedLanguage: Language;
45
+
46
+ constructor() {
47
+ // in the implementation, these should only be accessed in methods decorated with @InitializedOnly
48
+ this.#config = null!;
49
+ this.#eventHandlers = {
50
+ languageChange: new Set()
51
+ };
52
+ this.#resolvedLanguage = null!;
53
+ }
54
+
55
+ get isInitialized() {
56
+ return this.#config !== null;
57
+ }
58
+
59
+ @InitializedOnly
60
+ get resolvedLanguage() {
61
+ return this.#resolvedLanguage;
62
+ }
63
+
64
+ addEventListener<TKey extends keyof TranslatorEventMap>(key: TKey, handler: TranslatorEventMap[TKey]) {
65
+ this.#eventHandlers[key].add(handler);
66
+ }
67
+
68
+ @InitializedOnly
69
+ changeLanguage(language: Language) {
70
+ this.#resolvedLanguage = language;
71
+ document.documentElement.lang = language;
72
+ this.emitEvent('languageChange', [language]);
73
+ }
74
+
75
+ init({ defaultLanguage, translations }: TranslatorConfig) {
76
+ if (this.isInitialized) {
77
+ throw new Error('Cannot reinitialize Translator');
78
+ }
79
+ this.#config = {
80
+ defaultLanguage: defaultLanguage ?? 'en',
81
+ translations: {
82
+ libui,
83
+ ...translations
84
+ }
85
+ };
86
+ this.changeLanguage(this.#config.defaultLanguage);
87
+ }
88
+
89
+ removeEventListener<TKey extends keyof TranslatorEventMap>(key: TKey, handler: TranslatorEventMap[TKey]) {
90
+ return this.#eventHandlers[key].delete(handler);
91
+ }
92
+
93
+ @InitializedOnly
94
+ t(target: TranslationKey | { [L in Language]?: string }, { args }: TranslateOptions = {}): string {
95
+ let obj: { [key: string]: string };
96
+ if (typeof target === 'string') {
97
+ obj = (get(this.#config.translations, target) ?? {}) as { [key: string]: string };
98
+ } else {
99
+ obj = target;
100
+ }
101
+ const value = obj[this.#resolvedLanguage] ?? obj[this.#config.defaultLanguage];
102
+ if (!value) {
103
+ console.error(`Failed to extract translation from object '${JSON.stringify(obj)}'`);
104
+ return '';
105
+ }
106
+ if (!args) {
107
+ return value;
108
+ }
109
+ return format(value, ...this.getFormatArgs(args));
110
+ }
111
+
112
+ private emitEvent<TKey extends keyof TranslatorEventMap>(key: TKey, payload: Parameters<TranslatorEventMap[TKey]>) {
113
+ this.#eventHandlers[key].forEach((fn: (...args: any[]) => any) => {
114
+ fn(...payload);
115
+ });
116
+ }
117
+
118
+ private getFormatArgs(args: TranslateFormatArgs) {
119
+ if (Array.isArray(args)) {
120
+ return args;
121
+ }
122
+ const result = args[this.#resolvedLanguage] ?? args[this.#config.defaultLanguage];
123
+ if (!result) {
124
+ console.error(`Failed to extract args from object '${JSON.stringify(args)}'`);
125
+ return [];
126
+ }
127
+ return result;
128
+ }
129
+ }
package/src/i18n/types.ts CHANGED
@@ -41,11 +41,28 @@ export type ExtractTranslationKey<T extends { [key: string]: any }, Key = keyof
41
41
 
42
42
  export type TranslationNamespace = Extract<keyof Translations, string>;
43
43
 
44
- export type TranslationKey<TNamespace> = TNamespace extends TranslationNamespace
45
- ? ExtractTranslationKey<Translations[TNamespace]>
46
- : ExtractTranslationKey<Translations>;
44
+ export type TranslationKey = ExtractTranslationKey<Translations>;
47
45
 
48
- export interface TranslateFunction<TNamespace = undefined> {
49
- (key: TranslationKey<TNamespace>, ...args: Exclude<Primitive, symbol>[]): string;
50
- (translations: { [L in Language]?: string }, ...args: Exclude<Primitive, symbol>[]): string;
46
+ export type TranslationKeyForNamespace<TNamespace extends TranslationNamespace> =
47
+ TranslationKey extends `${TNamespace}.${infer TKey}` ? TKey : never;
48
+
49
+ export type TranslateFormatArgs =
50
+ | Exclude<Primitive, symbol>[]
51
+ | {
52
+ [L in Language]?: Exclude<Primitive, symbol>[];
53
+ };
54
+
55
+ export type TranslateOptions = {
56
+ args?: TranslateFormatArgs;
57
+ };
58
+
59
+ export interface TranslateFunction<TKey extends string> {
60
+ (key: TKey, options?: TranslateOptions): string;
61
+ (translations: { [L in Language]?: string }, options?: TranslateOptions): string;
51
62
  }
63
+
64
+ export type TranslatorType<TKey extends string> = {
65
+ changeLanguage: (language: Language) => void;
66
+ resolvedLanguage: Language;
67
+ t: TranslateFunction<TKey>;
68
+ };
@@ -1,4 +1,3 @@
1
- import { faker } from '@faker-js/faker';
2
1
  import { vi } from 'vitest';
3
2
 
4
3
  import type { StorageName } from '@/hooks';
@@ -51,11 +50,3 @@ export const mockStorage = (name: StorageName): void => {
51
50
  value: new StorageMock()
52
51
  });
53
52
  };
54
-
55
- export const mockTranslationStore = () => {
56
- vi.mock('@/hooks/useTranslation', () => ({
57
- useTranslation: () => ({
58
- t: () => faker.word.sample()
59
- })
60
- }));
61
- };
@@ -1,12 +1,13 @@
1
1
  import { cleanup } from '@testing-library/react';
2
2
  import { afterEach, vi } from 'vitest';
3
3
 
4
- import { mockTranslationStore } from './mocks';
4
+ import { i18n } from '@/i18n';
5
5
 
6
6
  import '@testing-library/jest-dom/vitest';
7
7
 
8
8
  vi.mock('zustand');
9
- mockTranslationStore();
9
+
10
+ i18n.init({ translations: {} });
10
11
 
11
12
  // Since we're not using vitest globals, we need to explicitly call cleanup()
12
13
  // for testing-library. See:
@@ -1,252 +0,0 @@
1
- "use client"
2
-
3
- // src/i18n/store.ts
4
- import { subscribeWithSelector } from "zustand/middleware";
5
- import { createStore } from "zustand/vanilla";
6
-
7
- // src/i18n/translations/libui.json
8
- var libui_default = {
9
- days: {
10
- friday: {
11
- en: "Friday",
12
- fr: "Vendredi"
13
- },
14
- monday: {
15
- en: "Monday",
16
- fr: "Lundi"
17
- },
18
- saturday: {
19
- en: "Saturday",
20
- fr: "Samedi"
21
- },
22
- sunday: {
23
- en: "Sunday",
24
- fr: "Dimanche"
25
- },
26
- thursday: {
27
- en: "Thursday",
28
- fr: "Jeudi"
29
- },
30
- tuesday: {
31
- en: "Tuesday",
32
- fr: "Mardi"
33
- },
34
- wednesday: {
35
- en: "Wednesday",
36
- fr: "Mercredi"
37
- }
38
- },
39
- form: {
40
- append: {
41
- en: "Append",
42
- fr: "Ajouter"
43
- },
44
- radioLabels: {
45
- false: {
46
- en: "False",
47
- fr: "Faux"
48
- },
49
- true: {
50
- en: "True",
51
- fr: "Vrai"
52
- }
53
- },
54
- remove: {
55
- en: "Remove",
56
- fr: "Supprimer"
57
- },
58
- required: {
59
- en: "This field is required",
60
- fr: "Ce champ est obligatoire"
61
- },
62
- reset: {
63
- en: "Reset",
64
- fr: "R\xE9initialiser"
65
- },
66
- submit: {
67
- en: "Submit",
68
- fr: "Soumettre"
69
- }
70
- },
71
- months: {
72
- april: {
73
- en: "April",
74
- fr: "Avril"
75
- },
76
- august: {
77
- en: "August",
78
- fr: "Ao\xFBt"
79
- },
80
- december: {
81
- en: "December",
82
- fr: "D\xE9cembre"
83
- },
84
- february: {
85
- en: "February",
86
- fr: "F\xE9vrier"
87
- },
88
- january: {
89
- en: "January",
90
- fr: "Janvier"
91
- },
92
- july: {
93
- en: "July",
94
- fr: "Juillet"
95
- },
96
- june: {
97
- en: "June",
98
- fr: "Juin"
99
- },
100
- march: {
101
- en: "March",
102
- fr: "Mars"
103
- },
104
- may: {
105
- en: "May",
106
- fr: "Mai"
107
- },
108
- november: {
109
- en: "November",
110
- fr: "Novembre"
111
- },
112
- october: {
113
- en: "October",
114
- fr: "Octobre"
115
- },
116
- september: {
117
- en: "September",
118
- fr: "Septembre"
119
- }
120
- },
121
- notifications: {
122
- types: {
123
- error: {
124
- en: "Error",
125
- fr: "Erreur"
126
- },
127
- info: {
128
- en: "Info",
129
- fr: "Attention"
130
- },
131
- success: {
132
- en: "Success",
133
- fr: "Succ\xE8s"
134
- },
135
- warning: {
136
- en: "Warning",
137
- fr: "Avertissement"
138
- }
139
- }
140
- },
141
- oneTimePasswordInput: {
142
- invalidCodeFormat: {
143
- en: "Invalid code format",
144
- fr: "Format de code invalide"
145
- }
146
- },
147
- pagination: {
148
- firstPage: {
149
- en: "<< First",
150
- fr: "<< Premi\xE8re"
151
- },
152
- first: {
153
- en: "First",
154
- fr: "Premi\xE8re"
155
- },
156
- info: {
157
- en: "Showing {{first}} to {{last}} of {{total}} results",
158
- fr: "Affichage de {{first}} \xE0 {{last}} sur {{total}} r\xE9sultats"
159
- },
160
- lastPage: {
161
- en: "Last >>",
162
- fr: "Derni\xE8re >>"
163
- },
164
- last: {
165
- en: "Last",
166
- fr: "Derni\xE8re"
167
- },
168
- next: {
169
- en: "Next",
170
- fr: "Suivant"
171
- },
172
- previous: {
173
- en: "Previous",
174
- fr: "Pr\xE9c\xE9dent"
175
- }
176
- },
177
- searchBar: {
178
- placeholder: {
179
- en: "Search...",
180
- fr: "Rechercher..."
181
- }
182
- },
183
- yes: {
184
- en: "Yes",
185
- fr: "Oui"
186
- },
187
- no: {
188
- en: "No",
189
- fr: "Non"
190
- }
191
- };
192
-
193
- // src/i18n/internal.ts
194
- import { format } from "@douglasneuroinformatics/libjs";
195
- import { get } from "lodash-es";
196
- function getTranslation(target, state, ...args) {
197
- let value;
198
- if (typeof target === "string") {
199
- value = get(state.translations, target);
200
- } else {
201
- value = target;
202
- }
203
- return format(value[state.resolvedLanguage] ?? value[state.fallbackLanguage], ...args);
204
- }
205
-
206
- // src/i18n/store.ts
207
- var translationStore = createStore(
208
- subscribeWithSelector((set) => ({
209
- changeLanguage(language) {
210
- set({ resolvedLanguage: language });
211
- },
212
- fallbackLanguage: "en",
213
- isInitialized: false,
214
- resolvedLanguage: "en",
215
- translations: { libui: libui_default }
216
- }))
217
- );
218
- var i18n = {
219
- init: ({ defaultLanguage, fallbackLanguage, translations } = {}) => {
220
- const state = translationStore.getState();
221
- if (state.isInitialized) {
222
- console.error("Cannot reinitialize translations store");
223
- return;
224
- }
225
- translationStore.subscribe(
226
- (state2) => state2.resolvedLanguage,
227
- (resolvedLanguage) => {
228
- document.documentElement.lang = resolvedLanguage;
229
- }
230
- );
231
- translationStore.setState({
232
- fallbackLanguage: fallbackLanguage ?? state.fallbackLanguage,
233
- isInitialized: true,
234
- resolvedLanguage: defaultLanguage ?? state.resolvedLanguage,
235
- translations: {
236
- ...state.translations,
237
- ...translations
238
- }
239
- });
240
- },
241
- t: (target, ...args) => {
242
- const state = translationStore.getState();
243
- return getTranslation(target, state, ...args);
244
- }
245
- };
246
-
247
- export {
248
- getTranslation,
249
- translationStore,
250
- i18n
251
- };
252
- //# sourceMappingURL=chunk-VFVO337W.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/i18n/store.ts","../src/i18n/translations/libui.json","../src/i18n/internal.ts"],"sourcesContent":["import type { SetOptional } from 'type-fest';\nimport { subscribeWithSelector } from 'zustand/middleware';\nimport { createStore } from 'zustand/vanilla';\n\nimport libui from '@/i18n/translations/libui.json';\n\nimport { getTranslation } from './internal';\n\nimport type { Language, TranslateFunction, Translations } from './types';\n\ntype InitOptions = {\n defaultLanguage?: Language;\n fallbackLanguage?: Language;\n translations?: SetOptional<Translations, 'libui'>;\n};\n\ntype I18N = {\n init: (options?: InitOptions) => void;\n t: TranslateFunction;\n};\n\nexport type TranslationStore = {\n changeLanguage: (language: Language) => void;\n fallbackLanguage: Language;\n isInitialized: boolean;\n resolvedLanguage: Language;\n translations: Translations;\n};\n\nexport const translationStore = createStore(\n subscribeWithSelector<TranslationStore>((set) => ({\n changeLanguage(language) {\n set({ resolvedLanguage: language });\n },\n fallbackLanguage: 'en',\n isInitialized: false,\n resolvedLanguage: 'en',\n translations: { libui }\n }))\n);\n\nexport const i18n: I18N = {\n init: ({ defaultLanguage, fallbackLanguage, translations }: InitOptions = {}) => {\n const state = translationStore.getState();\n if (state.isInitialized) {\n console.error('Cannot reinitialize translations store');\n return;\n }\n translationStore.subscribe(\n (state) => state.resolvedLanguage,\n (resolvedLanguage) => {\n document.documentElement.lang = resolvedLanguage;\n }\n );\n translationStore.setState({\n fallbackLanguage: fallbackLanguage ?? state.fallbackLanguage,\n isInitialized: true,\n resolvedLanguage: defaultLanguage ?? state.resolvedLanguage,\n translations: {\n ...state.translations,\n ...translations\n }\n });\n },\n t: (target, ...args) => {\n const state = translationStore.getState();\n return getTranslation(target, state, ...args);\n }\n};\n","{\n \"days\": {\n \"friday\": {\n \"en\": \"Friday\",\n \"fr\": \"Vendredi\"\n },\n \"monday\": {\n \"en\": \"Monday\",\n \"fr\": \"Lundi\"\n },\n \"saturday\": {\n \"en\": \"Saturday\",\n \"fr\": \"Samedi\"\n },\n \"sunday\": {\n \"en\": \"Sunday\",\n \"fr\": \"Dimanche\"\n },\n \"thursday\": {\n \"en\": \"Thursday\",\n \"fr\": \"Jeudi\"\n },\n \"tuesday\": {\n \"en\": \"Tuesday\",\n \"fr\": \"Mardi\"\n },\n \"wednesday\": {\n \"en\": \"Wednesday\",\n \"fr\": \"Mercredi\"\n }\n },\n \"form\": {\n \"append\": {\n \"en\": \"Append\",\n \"fr\": \"Ajouter\"\n },\n \"radioLabels\": {\n \"false\": {\n \"en\": \"False\",\n \"fr\": \"Faux\"\n },\n \"true\": {\n \"en\": \"True\",\n \"fr\": \"Vrai\"\n }\n },\n \"remove\": {\n \"en\": \"Remove\",\n \"fr\": \"Supprimer\"\n },\n \"required\": {\n \"en\": \"This field is required\",\n \"fr\": \"Ce champ est obligatoire\"\n },\n \"reset\": {\n \"en\": \"Reset\",\n \"fr\": \"Réinitialiser\"\n },\n \"submit\": {\n \"en\": \"Submit\",\n \"fr\": \"Soumettre\"\n }\n },\n \"months\": {\n \"april\": {\n \"en\": \"April\",\n \"fr\": \"Avril\"\n },\n \"august\": {\n \"en\": \"August\",\n \"fr\": \"Août\"\n },\n \"december\": {\n \"en\": \"December\",\n \"fr\": \"Décembre\"\n },\n \"february\": {\n \"en\": \"February\",\n \"fr\": \"Février\"\n },\n \"january\": {\n \"en\": \"January\",\n \"fr\": \"Janvier\"\n },\n \"july\": {\n \"en\": \"July\",\n \"fr\": \"Juillet\"\n },\n \"june\": {\n \"en\": \"June\",\n \"fr\": \"Juin\"\n },\n \"march\": {\n \"en\": \"March\",\n \"fr\": \"Mars\"\n },\n \"may\": {\n \"en\": \"May\",\n \"fr\": \"Mai\"\n },\n \"november\": {\n \"en\": \"November\",\n \"fr\": \"Novembre\"\n },\n \"october\": {\n \"en\": \"October\",\n \"fr\": \"Octobre\"\n },\n \"september\": {\n \"en\": \"September\",\n \"fr\": \"Septembre\"\n }\n },\n \"notifications\": {\n \"types\": {\n \"error\": {\n \"en\": \"Error\",\n \"fr\": \"Erreur\"\n },\n \"info\": {\n \"en\": \"Info\",\n \"fr\": \"Attention\"\n },\n \"success\": {\n \"en\": \"Success\",\n \"fr\": \"Succès\"\n },\n \"warning\": {\n \"en\": \"Warning\",\n \"fr\": \"Avertissement\"\n }\n }\n },\n \"oneTimePasswordInput\": {\n \"invalidCodeFormat\": {\n \"en\": \"Invalid code format\",\n \"fr\": \"Format de code invalide\"\n }\n },\n \"pagination\": {\n \"firstPage\": {\n \"en\": \"<< First\",\n \"fr\": \"<< Première\"\n },\n \"first\": {\n \"en\": \"First\",\n \"fr\": \"Première\"\n },\n \"info\": {\n \"en\": \"Showing {{first}} to {{last}} of {{total}} results\",\n \"fr\": \"Affichage de {{first}} à {{last}} sur {{total}} résultats\"\n },\n \"lastPage\": {\n \"en\": \"Last >>\",\n \"fr\": \"Dernière >>\"\n },\n \"last\": {\n \"en\": \"Last\",\n \"fr\": \"Dernière\"\n },\n \"next\": {\n \"en\": \"Next\",\n \"fr\": \"Suivant\"\n },\n \"previous\": {\n \"en\": \"Previous\",\n \"fr\": \"Précédent\"\n }\n },\n \"searchBar\": {\n \"placeholder\": {\n \"en\": \"Search...\",\n \"fr\": \"Rechercher...\"\n }\n },\n \"yes\": {\n \"en\": \"Yes\",\n \"fr\": \"Oui\"\n },\n \"no\": {\n \"en\": \"No\",\n \"fr\": \"Non\"\n }\n}\n","import { format } from '@douglasneuroinformatics/libjs';\nimport { get } from 'lodash-es';\nimport type { Primitive } from 'type-fest';\n\nimport type { Language } from './types';\n\nexport function getTranslation(\n target: string | { [L in Language]?: string },\n state: {\n fallbackLanguage: Language;\n resolvedLanguage: Language;\n translations: { [key: string]: any };\n },\n ...args: Exclude<Primitive, symbol>[]\n) {\n let value: { [key: string]: string };\n if (typeof target === 'string') {\n value = get(state.translations, target) as { [key: string]: string };\n } else {\n value = target;\n }\n return format((value[state.resolvedLanguage] ?? value[state.fallbackLanguage])!, ...args);\n}\n"],"mappings":"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","names":["state"]}
@@ -1,23 +0,0 @@
1
- import { format } from '@douglasneuroinformatics/libjs';
2
- import { get } from 'lodash-es';
3
- import type { Primitive } from 'type-fest';
4
-
5
- import type { Language } from './types';
6
-
7
- export function getTranslation(
8
- target: string | { [L in Language]?: string },
9
- state: {
10
- fallbackLanguage: Language;
11
- resolvedLanguage: Language;
12
- translations: { [key: string]: any };
13
- },
14
- ...args: Exclude<Primitive, symbol>[]
15
- ) {
16
- let value: { [key: string]: string };
17
- if (typeof target === 'string') {
18
- value = get(state.translations, target) as { [key: string]: string };
19
- } else {
20
- value = target;
21
- }
22
- return format((value[state.resolvedLanguage] ?? value[state.fallbackLanguage])!, ...args);
23
- }
package/src/i18n/store.ts DELETED
@@ -1,69 +0,0 @@
1
- import type { SetOptional } from 'type-fest';
2
- import { subscribeWithSelector } from 'zustand/middleware';
3
- import { createStore } from 'zustand/vanilla';
4
-
5
- import libui from '@/i18n/translations/libui.json';
6
-
7
- import { getTranslation } from './internal';
8
-
9
- import type { Language, TranslateFunction, Translations } from './types';
10
-
11
- type InitOptions = {
12
- defaultLanguage?: Language;
13
- fallbackLanguage?: Language;
14
- translations?: SetOptional<Translations, 'libui'>;
15
- };
16
-
17
- type I18N = {
18
- init: (options?: InitOptions) => void;
19
- t: TranslateFunction;
20
- };
21
-
22
- export type TranslationStore = {
23
- changeLanguage: (language: Language) => void;
24
- fallbackLanguage: Language;
25
- isInitialized: boolean;
26
- resolvedLanguage: Language;
27
- translations: Translations;
28
- };
29
-
30
- export const translationStore = createStore(
31
- subscribeWithSelector<TranslationStore>((set) => ({
32
- changeLanguage(language) {
33
- set({ resolvedLanguage: language });
34
- },
35
- fallbackLanguage: 'en',
36
- isInitialized: false,
37
- resolvedLanguage: 'en',
38
- translations: { libui }
39
- }))
40
- );
41
-
42
- export const i18n: I18N = {
43
- init: ({ defaultLanguage, fallbackLanguage, translations }: InitOptions = {}) => {
44
- const state = translationStore.getState();
45
- if (state.isInitialized) {
46
- console.error('Cannot reinitialize translations store');
47
- return;
48
- }
49
- translationStore.subscribe(
50
- (state) => state.resolvedLanguage,
51
- (resolvedLanguage) => {
52
- document.documentElement.lang = resolvedLanguage;
53
- }
54
- );
55
- translationStore.setState({
56
- fallbackLanguage: fallbackLanguage ?? state.fallbackLanguage,
57
- isInitialized: true,
58
- resolvedLanguage: defaultLanguage ?? state.resolvedLanguage,
59
- translations: {
60
- ...state.translations,
61
- ...translations
62
- }
63
- });
64
- },
65
- t: (target, ...args) => {
66
- const state = translationStore.getState();
67
- return getTranslation(target, state, ...args);
68
- }
69
- };