piral-translate 1.0.0-pre.2217 → 1.0.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 (53) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +12 -2
  3. package/esm/Languages.js +2 -2
  4. package/esm/Languages.js.map +1 -1
  5. package/esm/actions.js +17 -19
  6. package/esm/actions.js.map +1 -1
  7. package/esm/components.d.ts +2 -3
  8. package/esm/components.js +1 -1
  9. package/esm/components.js.map +1 -1
  10. package/esm/create.js +39 -19
  11. package/esm/create.js.map +1 -1
  12. package/esm/current.js +2 -2
  13. package/esm/current.js.map +1 -1
  14. package/esm/default.d.ts +2 -2
  15. package/esm/default.js +1 -1
  16. package/esm/default.js.map +1 -1
  17. package/esm/hooks.js +12 -10
  18. package/esm/hooks.js.map +1 -1
  19. package/esm/localize.d.ts +2 -2
  20. package/esm/localize.js +20 -23
  21. package/esm/localize.js.map +1 -1
  22. package/esm/types.d.ts +24 -4
  23. package/lib/Languages.js +6 -5
  24. package/lib/Languages.js.map +1 -1
  25. package/lib/actions.js +17 -19
  26. package/lib/actions.js.map +1 -1
  27. package/lib/components.d.ts +2 -3
  28. package/lib/components.js +2 -2
  29. package/lib/components.js.map +1 -1
  30. package/lib/create.js +43 -23
  31. package/lib/create.js.map +1 -1
  32. package/lib/current.js +3 -3
  33. package/lib/current.js.map +1 -1
  34. package/lib/default.d.ts +2 -2
  35. package/lib/default.js +3 -2
  36. package/lib/default.js.map +1 -1
  37. package/lib/hooks.js +14 -12
  38. package/lib/hooks.js.map +1 -1
  39. package/lib/index.js +1 -1
  40. package/lib/localize.d.ts +2 -2
  41. package/lib/localize.js +20 -22
  42. package/lib/localize.js.map +1 -1
  43. package/lib/types.d.ts +24 -4
  44. package/package.json +33 -6
  45. package/piral-translate.min.js +1 -0
  46. package/src/actions.test.ts +134 -5
  47. package/src/components.tsx +1 -3
  48. package/src/create.test.ts +122 -3
  49. package/src/create.ts +32 -3
  50. package/src/default.tsx +2 -1
  51. package/src/hooks.ts +3 -1
  52. package/src/localize.ts +5 -5
  53. package/src/types.ts +24 -5
@@ -1,17 +1,18 @@
1
- import { Atom, deref } from '@dbeining/react-atom';
1
+ import create from 'zustand';
2
+ import { createListener } from 'piral-base';
3
+ import { createActions as ca } from 'piral-core';
2
4
  import { createActions } from './actions';
3
- import { createActions as ca, createListener } from 'piral-core';
4
5
 
5
6
  describe('Translation Action Module', () => {
6
7
  it('selectLanguage changes the current language', () => {
7
- const state = Atom.of({
8
+ const state: any = create(() => ({
8
9
  foo: 5,
9
10
  language: {
10
11
  foo: 10,
11
12
  loading: false,
12
13
  selected: 'fr',
13
14
  },
14
- });
15
+ }));
15
16
  const localizer = {
16
17
  language: 'en',
17
18
  languages: ['en'],
@@ -26,7 +27,7 @@ describe('Translation Action Module', () => {
26
27
  const actions = createActions(localizer);
27
28
  const ctx = ca(state, createListener({}));
28
29
  actions.selectLanguage(ctx, 'de');
29
- expect(deref(state)).toEqual({
30
+ expect((state.getState())).toEqual({
30
31
  foo: 5,
31
32
  language: {
32
33
  foo: 10,
@@ -35,4 +36,132 @@ describe('Translation Action Module', () => {
35
36
  },
36
37
  });
37
38
  });
39
+
40
+ it('translate', () => {
41
+ const state: any = create(() => ({
42
+ foo: 5,
43
+ language: {
44
+ foo: 10,
45
+ loading: false,
46
+ selected: 'fr',
47
+ },
48
+ }));
49
+ const localizer = {
50
+ language: 'fr',
51
+ languages: ['fr'],
52
+ messages: {
53
+ fr: {
54
+ bar: 'bár',
55
+ },
56
+ },
57
+ localizeGlobal(key, variables) {
58
+ const messages = {
59
+ fr: {
60
+ bar: 'bár',
61
+ },
62
+ };
63
+ return messages.fr[key];
64
+ },
65
+ localizeLocal() {
66
+ return '';
67
+ },
68
+ };
69
+ const actions = createActions(localizer);
70
+ const ctx = ca(state, createListener({}));
71
+ const result = actions.translate(ctx, 'bar');
72
+ expect(result).toEqual('bár');
73
+ });
74
+
75
+ it('setTranslations sets translations to the global translations', () => {
76
+ const state: any = create(() => ({
77
+ foo: 5,
78
+ language: {
79
+ foo: 10,
80
+ loading: false,
81
+ selected: 'fr',
82
+ },
83
+ }));
84
+ const localizer = {
85
+ language: 'de',
86
+ languages: ['de'],
87
+ messages: {
88
+ de: {},
89
+ },
90
+ localizeGlobal() {
91
+ return '';
92
+ },
93
+ localizeLocal() {
94
+ return '';
95
+ },
96
+ };
97
+ const ctx = {
98
+ emit: jest.fn(),
99
+ state,
100
+ dispatch(update) {
101
+ state.setState(update(state.getState()));
102
+ },
103
+ apis: {
104
+ firstApi: {
105
+ getTranslations: () => {
106
+ return localizer.messages;
107
+ },
108
+ setTranslations: async (translations) => {
109
+ localizer.messages = await translations;
110
+ },
111
+ },
112
+ },
113
+ };
114
+ const actions = createActions(localizer);
115
+ const data = {
116
+ global: {},
117
+ locals: [{ name: 'firstApi', value: { car: 'Auto', table: 'Tisch' } }],
118
+ };
119
+ actions.setTranslations(ctx, 'de', data);
120
+ expect(localizer.messages).toEqual({
121
+ de: { car: 'Auto', table: 'Tisch' },
122
+ });
123
+ });
124
+
125
+ it('getTranslations returns translations', () => {
126
+ const state: any = create(() => ({
127
+ foo: 5,
128
+ language: {
129
+ foo: 10,
130
+ loading: false,
131
+ selected: 'fr',
132
+ },
133
+ }));
134
+ const localizer = {
135
+ language: 'fr',
136
+ languages: ['fr'],
137
+ messages: {
138
+ fr: {
139
+ foo: 'bár',
140
+ },
141
+ },
142
+ localizeGlobal() {
143
+ return '';
144
+ },
145
+ localizeLocal() {
146
+ return '';
147
+ },
148
+ };
149
+ const actions = createActions(localizer);
150
+ const ctx = {
151
+ emit: jest.fn(),
152
+ state,
153
+ dispatch(update) {
154
+ state.setState(update(state.getState()));
155
+ },
156
+ apis: {
157
+ firstApi: {
158
+ getTranslations: () => {
159
+ return localizer.messages;
160
+ },
161
+ },
162
+ },
163
+ };
164
+ const result = actions.getTranslations(ctx, 'fr');
165
+ expect(result).toEqual({ global: { foo: 'bár' }, locals: [{ name: 'firstApi', value: { foo: 'bár' } }] });
166
+ });
38
167
  });
@@ -1,5 +1,3 @@
1
- import * as React from 'react';
2
1
  import { getPiralComponent } from 'piral-core';
3
- import { LanguagesPickerProps } from './types';
4
2
 
5
- export const PiralLanguagesPicker: React.ComponentType<LanguagesPickerProps> = getPiralComponent('LanguagesPicker');
3
+ export const PiralLanguagesPicker = getPiralComponent('LanguagesPicker');
@@ -1,13 +1,16 @@
1
- import { Atom, swap } from '@dbeining/react-atom';
1
+ import create from 'zustand';
2
2
  import { createLocaleApi, setupLocalizer } from './create';
3
3
 
4
4
  describe('Create Localize API', () => {
5
- const state = Atom.of({});
5
+ const state = create(() => ({}));
6
6
  const context: any = {
7
7
  defineActions() {},
8
8
  state,
9
+ readState(cb) {
10
+ return cb(state.getState());
11
+ },
9
12
  dispatch(update) {
10
- swap(state, update);
13
+ state.setState(update(state.getState()));
11
14
  },
12
15
  };
13
16
 
@@ -95,4 +98,120 @@ describe('Create Localize API', () => {
95
98
  const result = api.translate('qxz');
96
99
  expect(result).toEqual('__fr_qxz__');
97
100
  });
101
+
102
+ it('getTranslations return the translations', () => {
103
+ const config = {
104
+ language: 'fr',
105
+ messages: {
106
+ fr: {
107
+ foo: 'bár',
108
+ bar: 'bár',
109
+ },
110
+ },
111
+ };
112
+ const api = (createLocaleApi(setupLocalizer(config))(context) as any)();
113
+ api.setTranslations({
114
+ fr: {
115
+ foo: 'boo',
116
+ },
117
+ });
118
+ const result = api.getTranslations();
119
+ expect(result).toEqual({ fr: { foo: 'boo' } });
120
+ });
121
+
122
+ it('getCurrentLanguage return the current language', () => {
123
+ const config = {
124
+ language: 'fr',
125
+ messages: {
126
+ fr: {
127
+ foo: 'bár',
128
+ bar: 'bár',
129
+ },
130
+ },
131
+ };
132
+ const api = (createLocaleApi(setupLocalizer(config))(context) as any)();
133
+ const result = api.getCurrentLanguage();
134
+ expect(result).toEqual('fr');
135
+ });
136
+
137
+ it('addTranslations should add new translations if no value exists yet for the combination of language and key', (): void => {
138
+ const config = {
139
+ language: 'fr',
140
+ messages: {
141
+ fr: {
142
+ foo: 'fóo',
143
+ bar: 'bár',
144
+ },
145
+ },
146
+ };
147
+ const messagesToAdd = {
148
+ fr: {
149
+ foo: 'fóo (new)',
150
+ bar: 'bár (new)',
151
+ baz: 'báz (new)',
152
+ },
153
+ };
154
+ const api = (createLocaleApi(setupLocalizer(config))(context) as any)();
155
+ api.addTranslations([
156
+ messagesToAdd
157
+ ]);
158
+ const result = api.translate('baz');
159
+
160
+ expect(result).toEqual(messagesToAdd.fr.baz);
161
+ });
162
+
163
+ it('addTranslations should add new translations if overwriting is enabled and a value already exists for the combination of language and key', (): void => {
164
+ const config = {
165
+ language: 'fr',
166
+ messages: {
167
+ fr: {
168
+ foo: 'fóo',
169
+ bar: 'bár',
170
+ },
171
+ },
172
+ };
173
+ const messagesToAdd = {
174
+ fr: {
175
+ foo: 'fóo (new)',
176
+ bar: 'bár (new)',
177
+ baz: 'báz (new)',
178
+ },
179
+ };
180
+ const api = (createLocaleApi(setupLocalizer(config))(context) as any)();
181
+ api.addTranslations([
182
+ messagesToAdd
183
+ ]);
184
+ const result = api.translate('bar');
185
+
186
+ expect(result).toEqual(messagesToAdd.fr.bar);
187
+ });
188
+
189
+ it('addTranslations should not add new translations if overwriting is disabled and a value already exists for the combination of language and key', (): void => {
190
+ const config = {
191
+ language: 'fr',
192
+ messages: {
193
+ fr: {
194
+ foo: 'fóo',
195
+ bar: 'bár',
196
+ },
197
+ },
198
+ };
199
+ const messagesToAdd = {
200
+ fr: {
201
+ foo: 'fóo (neu)',
202
+ bar: 'bár (neu)',
203
+ baz: 'báz',
204
+ },
205
+ };
206
+ const api = (createLocaleApi(setupLocalizer(config))(context) as any)();
207
+ api.addTranslations(
208
+ [
209
+ messagesToAdd
210
+ ],
211
+ false
212
+ );
213
+ const result = api.translate('bar');
214
+
215
+ expect(result).toEqual(config.messages.fr.bar);
216
+ });
98
217
  });
package/src/create.ts CHANGED
@@ -1,8 +1,10 @@
1
+ import * as deepmerge from 'deepmerge';
2
+
1
3
  import type { PiralPlugin } from 'piral-core';
2
4
  import { createActions } from './actions';
3
5
  import { Localizer } from './localize';
4
6
  import { DefaultPicker } from './default';
5
- import { PiletLocaleApi, LocalizationMessages, Localizable } from './types';
7
+ import { PiletLocaleApi, LocalizationMessages, Localizable, PiralSelectLanguageEvent } from './types';
6
8
 
7
9
  export interface TranslationFallback {
8
10
  (key: string, language: string): string;
@@ -56,8 +58,8 @@ export function createLocaleApi(localizer: Localizable = setupLocalizer()): Pira
56
58
  context.dispatch((state) => ({
57
59
  ...state,
58
60
  components: {
59
- ...state.components,
60
61
  LanguagesPicker: DefaultPicker,
62
+ ...state.components,
61
63
  },
62
64
  language: {
63
65
  loading: false,
@@ -66,10 +68,37 @@ export function createLocaleApi(localizer: Localizable = setupLocalizer()): Pira
66
68
  },
67
69
  }));
68
70
 
69
- return () => {
71
+ return (api) => {
70
72
  let localTranslations: LocalizationMessages = {};
71
73
 
72
74
  return {
75
+ addTranslations(messages: LocalizationMessages[], isOverriding: boolean = true) {
76
+ const messagesToMerge: LocalizationMessages[] = messages;
77
+
78
+ if (isOverriding) {
79
+ messagesToMerge.unshift(localizer.messages);
80
+ } else {
81
+ messagesToMerge.push(localizer.messages);
82
+ }
83
+
84
+ this.setTranslations(
85
+ deepmerge.all(messagesToMerge),
86
+ );
87
+ },
88
+ getCurrentLanguage(cb?: (l: string) => void): any {
89
+ const selected = context.readState((s) => s.language.selected);
90
+
91
+ if (cb) {
92
+ cb(selected);
93
+ const handler = (ev: PiralSelectLanguageEvent) => {
94
+ cb(ev.currentLanguage);
95
+ };
96
+ api.on('select-language', handler);
97
+ return () => api.off('select-language', handler);
98
+ }
99
+
100
+ return selected;
101
+ },
73
102
  setTranslations(messages) {
74
103
  localTranslations = messages;
75
104
  },
package/src/default.tsx CHANGED
@@ -1,4 +1,5 @@
1
+ import type { FC } from 'react';
1
2
  import { defaultRender } from 'piral-core';
2
3
  import { LanguagesPickerProps } from './types';
3
4
 
4
- export const DefaultPicker: React.FC<LanguagesPickerProps> = (props) => defaultRender(undefined);
5
+ export const DefaultPicker: FC<LanguagesPickerProps> = (props) => defaultRender(undefined);
package/src/hooks.ts CHANGED
@@ -22,7 +22,9 @@ export function useDynamicLanguage(
22
22
  },
23
23
  (err) => console.error(err),
24
24
  );
25
- return () => (active = false);
25
+ return () => {
26
+ active = false;
27
+ };
26
28
  }, [selected]);
27
29
 
28
30
  return [selected, setSelected];
package/src/localize.ts CHANGED
@@ -13,7 +13,7 @@ function defaultFallback(key: string, language: string): string {
13
13
  }
14
14
  }
15
15
 
16
- function formatMessage<T>(message: string, variables: T): string {
16
+ function formatMessage<T extends object>(message: string, variables: T): string {
17
17
  return message.replace(/{{\s*([A-Za-z0-9_.]+)\s*}}/g, (_match: string, p1: string) => {
18
18
  return p1 in variables ? variables[p1] || '' : `{{${p1}}}`;
19
19
  });
@@ -35,7 +35,7 @@ export class Localizer implements Localizable {
35
35
  * @param key The key of the translation snippet.
36
36
  * @param variables The optional variables to use.
37
37
  */
38
- public localizeGlobal<T>(key: string, variables?: T) {
38
+ public localizeGlobal<T extends object>(key: string, variables?: T) {
39
39
  return this.localizeBase(key, variables);
40
40
  }
41
41
 
@@ -46,7 +46,7 @@ export class Localizer implements Localizable {
46
46
  * @param key The key of the translation snippet.
47
47
  * @param variables The optional variables to use.
48
48
  */
49
- public localizeLocal<T>(localMessages: LocalizationMessages, key: string, variables?: T) {
49
+ public localizeLocal<T extends object>(localMessages: LocalizationMessages, key: string, variables?: T) {
50
50
  const message = this.translateMessage(localMessages, key, variables);
51
51
 
52
52
  if (message === undefined) {
@@ -56,7 +56,7 @@ export class Localizer implements Localizable {
56
56
  return message;
57
57
  }
58
58
 
59
- private localizeBase<T>(key: string, variables?: T) {
59
+ private localizeBase<T extends object>(key: string, variables?: T) {
60
60
  const message = this.translateMessage(this.messages, key, variables);
61
61
 
62
62
  if (message === undefined) {
@@ -66,7 +66,7 @@ export class Localizer implements Localizable {
66
66
  return message;
67
67
  }
68
68
 
69
- private translateMessage<T>(messages: LocalizationMessages, key: string, variables?: T) {
69
+ private translateMessage<T extends object>(messages: LocalizationMessages, key: string, variables?: T) {
70
70
  const language = this.language;
71
71
  const translations = language && messages[language];
72
72
  const translation = translations && translations[key];
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type {} from 'piral-core';
2
- import { ComponentType } from 'react';
1
+ import type { Disposable } from 'piral-core';
2
+ import type { ComponentType } from 'react';
3
3
 
4
4
  declare module 'piral-core/lib/types/custom' {
5
5
  interface PiletCustomApi extends PiletLocaleApi {}
@@ -108,8 +108,8 @@ export interface Localizable {
108
108
  messages: LocalizationMessages;
109
109
  language: string;
110
110
  languages: Array<string>;
111
- localizeGlobal<T>(key: string, variables?: T): string;
112
- localizeLocal<T>(localMessages: LocalizationMessages, key: string, variables?: T): string;
111
+ localizeGlobal<T extends object>(key: string, variables?: T): string;
112
+ localizeLocal<T extends object>(localMessages: LocalizationMessages, key: string, variables?: T): string;
113
113
  }
114
114
 
115
115
  export interface LocalizationMessages {
@@ -120,13 +120,32 @@ export interface LocalizationMessages {
120
120
  }
121
121
 
122
122
  export interface PiletLocaleApi {
123
+ /**
124
+ * Adds a list of translations to the existing translations.
125
+ *
126
+ * Internally, setTranslations is used, which means the translations will be exclusively used for
127
+ * retrieving translations for the pilet.
128
+ *
129
+ * @param messagesList The list of messages that extend the existing translations
130
+ * @param [isOverriding=true] Indicates whether the new translations overwrite the existing translations
131
+ */
132
+ addTranslations(messagesList: LocalizationMessages[], isOverriding?: boolean): void;
133
+ /**
134
+ * Gets the currently selected language directly.
135
+ */
136
+ getCurrentLanguage(): string;
137
+ /**
138
+ * Gets the currently selected language in a callback that is also invoked when the
139
+ * selected language changes. Returns a disposable to stop the notifications.
140
+ */
141
+ getCurrentLanguage(cb: (currently: string) => void): Disposable;
123
142
  /**
124
143
  * Translates the given tag (using the optional variables) into a string using the current language.
125
144
  * The used template can contain placeholders in form of `{{variableName}}`.
126
145
  * @param tag The tag to translate.
127
146
  * @param variables The optional variables to fill into the temnplate.
128
147
  */
129
- translate<T = Record<string, string>>(tag: string, variables?: T): string;
148
+ translate<T extends object = Record<string, string>>(tag: string, variables?: T): string;
130
149
  /**
131
150
  * Provides translations to the application.
132
151
  * The translations will be exclusively used for retrieving translations for the pilet.