@tolgee/core 5.0.0-rc.9be0f0e.0 → 5.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.
- package/README.md +174 -0
- package/README.njk.md +61 -0
- package/dist/tolgee.cjs.js +723 -351
- package/dist/tolgee.cjs.js.map +1 -1
- package/dist/tolgee.cjs.min.js +1 -1
- package/dist/tolgee.cjs.min.js.map +1 -1
- package/dist/{tolgee.esm.mjs → tolgee.esm.js} +722 -346
- package/dist/tolgee.esm.js.map +1 -0
- package/dist/tolgee.esm.min.mjs +1 -1
- package/dist/tolgee.esm.min.mjs.map +1 -1
- package/dist/tolgee.umd.js +723 -351
- package/dist/tolgee.umd.js.map +1 -1
- package/dist/tolgee.umd.min.js +1 -1
- package/dist/tolgee.umd.min.js.map +1 -1
- package/lib/Controller/Cache/Cache.d.ts +10 -9
- package/lib/Controller/Controller.d.ts +104 -45
- package/lib/Controller/Events/EventEmitter.d.ts +6 -0
- package/lib/Controller/Events/EventEmitterSelective.d.ts +7 -0
- package/lib/Controller/Events/Events.d.ts +14 -0
- package/lib/Controller/Plugins/Plugins.d.ts +12 -25
- package/lib/Controller/State/State.d.ts +27 -9
- package/lib/Controller/State/initState.d.ts +46 -15
- package/lib/Controller/State/observerOptions.d.ts +41 -0
- package/lib/Controller/ValueObserver.d.ts +5 -5
- package/lib/FormatSimple/FormatError.d.ts +7 -0
- package/lib/FormatSimple/FormatSimple.d.ts +2 -0
- package/lib/FormatSimple/formatParser.d.ts +1 -0
- package/lib/FormatSimple/formatter.d.ts +2 -0
- package/lib/TolgeeCore.d.ts +204 -0
- package/lib/TranslateParams.d.ts +1 -1
- package/lib/helpers.d.ts +8 -0
- package/lib/index.d.ts +4 -4
- package/lib/types/cache.d.ts +25 -0
- package/lib/types/events.d.ts +66 -0
- package/lib/types/general.d.ts +34 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/plugin.d.ts +127 -0
- package/package.json +5 -4
- package/src/Controller/Cache/Cache.ts +31 -31
- package/src/Controller/Cache/helpers.ts +6 -6
- package/src/Controller/Controller.ts +78 -50
- package/src/Controller/Events/EventEmitter.ts +34 -0
- package/src/Controller/Events/EventEmitterSelective.test.ts +110 -0
- package/src/Controller/Events/EventEmitterSelective.ts +132 -0
- package/src/Controller/Events/Events.ts +69 -0
- package/src/Controller/Plugins/Plugins.ts +182 -133
- package/src/Controller/State/State.ts +43 -26
- package/src/Controller/State/initState.ts +97 -25
- package/src/Controller/State/observerOptions.ts +66 -0
- package/src/Controller/ValueObserver.ts +5 -2
- package/src/FormatSimple/FormatError.ts +26 -0
- package/src/FormatSimple/FormatSimple.ts +13 -0
- package/src/FormatSimple/formatParser.ts +133 -0
- package/src/FormatSimple/formatter.test.ts +190 -0
- package/src/FormatSimple/formatter.ts +19 -0
- package/src/TolgeeCore.ts +267 -0
- package/src/TranslateParams.test.ts +9 -12
- package/src/TranslateParams.ts +6 -5
- package/src/__test/backend.test.ts +6 -6
- package/src/__test/cache.test.ts +190 -0
- package/src/__test/client.test.ts +2 -2
- package/src/__test/events.test.ts +32 -7
- package/src/__test/format.simple.test.ts +14 -0
- package/src/__test/formatError.test.ts +61 -0
- package/src/__test/initialization.test.ts +15 -3
- package/src/__test/languageDetection.test.ts +14 -8
- package/src/__test/languageStorage.test.ts +10 -11
- package/src/__test/languages.test.ts +30 -6
- package/src/__test/loading.test.ts +2 -2
- package/src/__test/{namespacesFallback.test.ts → namespaces.fallback.test.ts} +10 -8
- package/src/__test/namespaces.test.ts +30 -7
- package/src/__test/options.test.ts +64 -0
- package/src/__test/plugins.test.ts +29 -18
- package/src/helpers.ts +53 -0
- package/src/index.ts +4 -10
- package/src/types/cache.ts +37 -0
- package/src/types/events.ts +85 -0
- package/src/types/general.ts +50 -0
- package/src/types/index.ts +19 -0
- package/src/types/plugin.ts +181 -0
- package/dist/tolgee.esm.mjs.map +0 -1
- package/lib/Controller/State/helpers.d.ts +0 -6
- package/lib/Events/EventEmitter.d.ts +0 -6
- package/lib/Events/EventEmitterSelective.d.ts +0 -15
- package/lib/Events/Events.d.ts +0 -50
- package/lib/Tolgee.d.ts +0 -2
- package/lib/constants.d.ts +0 -5
- package/lib/types.d.ts +0 -274
- package/src/Controller/State/helpers.ts +0 -41
- package/src/Events/EventEmitter.ts +0 -27
- package/src/Events/EventEmitterSelective.test.ts +0 -108
- package/src/Events/EventEmitterSelective.ts +0 -160
- package/src/Events/Events.ts +0 -66
- package/src/Tolgee.ts +0 -77
- package/src/constants.ts +0 -7
- package/src/types.ts +0 -380
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CacheDescriptor,
|
|
3
3
|
CacheDescriptorInternal,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
DevCredentials,
|
|
5
|
+
NsFallback,
|
|
6
|
+
NsType,
|
|
6
7
|
} from '../../types';
|
|
8
|
+
|
|
7
9
|
import { decodeCacheKey } from '../Cache/helpers';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { EventEmitterInstance } from '../Events/EventEmitter';
|
|
11
|
+
import {
|
|
12
|
+
getFallbackArray,
|
|
13
|
+
getFallbackFromStruct,
|
|
14
|
+
sanitizeUrl,
|
|
15
|
+
unique,
|
|
16
|
+
} from '../../helpers';
|
|
17
|
+
import { initState, TolgeeOptions } from './initState';
|
|
10
18
|
|
|
11
19
|
export const State = (
|
|
12
|
-
onLanguageChange:
|
|
13
|
-
onPendingLanguageChange:
|
|
14
|
-
onRunningChange:
|
|
20
|
+
onLanguageChange: EventEmitterInstance<string>,
|
|
21
|
+
onPendingLanguageChange: EventEmitterInstance<string>,
|
|
22
|
+
onRunningChange: EventEmitterInstance<boolean>
|
|
15
23
|
) => {
|
|
16
24
|
let state = initState();
|
|
25
|
+
let devCredentials: DevCredentials = undefined;
|
|
17
26
|
|
|
18
|
-
function init(options?: Partial<
|
|
27
|
+
function init(options?: Partial<TolgeeOptions>) {
|
|
19
28
|
state = initState(options, state);
|
|
20
29
|
}
|
|
21
30
|
|
|
@@ -42,14 +51,6 @@ export const State = (
|
|
|
42
51
|
return state.language || state.initialOptions.language;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
function getLanguageOrFail() {
|
|
46
|
-
const language = state.language || state.initialOptions.language;
|
|
47
|
-
if (!language) {
|
|
48
|
-
throw new Error(`No language set`);
|
|
49
|
-
}
|
|
50
|
-
return language;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
54
|
function setLanguage(language: string) {
|
|
54
55
|
if (state.language !== language) {
|
|
55
56
|
state.language = language;
|
|
@@ -69,10 +70,10 @@ export const State = (
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
function getInitialOptions() {
|
|
72
|
-
return state.initialOptions;
|
|
73
|
+
return { ...state.initialOptions, ...devCredentials };
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
function addActiveNs(ns:
|
|
76
|
+
function addActiveNs(ns: NsFallback) {
|
|
76
77
|
const namespaces = getFallbackArray(ns);
|
|
77
78
|
namespaces.forEach((namespace) => {
|
|
78
79
|
const value = state.activeNamespaces.get(namespace);
|
|
@@ -84,7 +85,7 @@ export const State = (
|
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function removeActiveNs(ns:
|
|
88
|
+
function removeActiveNs(ns: NsFallback) {
|
|
88
89
|
const namespaces = getFallbackArray(ns);
|
|
89
90
|
namespaces.forEach((namespace) => {
|
|
90
91
|
const value = state.activeNamespaces.get(namespace);
|
|
@@ -99,6 +100,7 @@ export const State = (
|
|
|
99
100
|
function getRequiredNamespaces() {
|
|
100
101
|
return unique([
|
|
101
102
|
...(state.initialOptions.ns || [state.initialOptions.defaultNs]),
|
|
103
|
+
...getFallbackArray(state.initialOptions.fallbackNs),
|
|
102
104
|
...state.activeNamespaces.keys(),
|
|
103
105
|
]);
|
|
104
106
|
}
|
|
@@ -114,11 +116,12 @@ export const State = (
|
|
|
114
116
|
]);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
function getFallbackNs() {
|
|
120
|
+
return getFallbackArray(state.initialOptions.fallbackNs);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getDefaultNs(ns?: NsType) {
|
|
124
|
+
return ns === undefined ? state.initialOptions.defaultNs : ns;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
function getAvailableLanguages() {
|
|
@@ -142,6 +145,17 @@ export const State = (
|
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
function overrideCredentials(credentials: DevCredentials) {
|
|
149
|
+
if (credentials) {
|
|
150
|
+
devCredentials = {
|
|
151
|
+
...credentials,
|
|
152
|
+
apiUrl: sanitizeUrl(credentials.apiUrl),
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
devCredentials = undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
return Object.freeze({
|
|
146
160
|
init,
|
|
147
161
|
isRunning,
|
|
@@ -149,7 +163,6 @@ export const State = (
|
|
|
149
163
|
isInitialLoading,
|
|
150
164
|
setInitialLoading,
|
|
151
165
|
getLanguage,
|
|
152
|
-
getLanguageOrFail,
|
|
153
166
|
setLanguage,
|
|
154
167
|
getPendingLanguage,
|
|
155
168
|
setPendingLanguage,
|
|
@@ -158,8 +171,12 @@ export const State = (
|
|
|
158
171
|
removeActiveNs,
|
|
159
172
|
getRequiredNamespaces,
|
|
160
173
|
getFallbackLangs,
|
|
161
|
-
|
|
174
|
+
getFallbackNs,
|
|
175
|
+
getDefaultNs,
|
|
162
176
|
getAvailableLanguages,
|
|
163
177
|
withDefaultNs,
|
|
178
|
+
overrideCredentials,
|
|
164
179
|
});
|
|
165
180
|
};
|
|
181
|
+
|
|
182
|
+
export type StateInstance = ReturnType<typeof State>;
|
|
@@ -1,57 +1,111 @@
|
|
|
1
1
|
import {
|
|
2
|
+
FallbackGeneral,
|
|
2
3
|
FallbackLanguageOption,
|
|
3
|
-
FallbackNS,
|
|
4
4
|
TreeTranslationsData,
|
|
5
|
+
OnFormatError,
|
|
5
6
|
} from '../../types';
|
|
7
|
+
import { sanitizeUrl } from '../../helpers';
|
|
8
|
+
import {
|
|
9
|
+
defaultObserverOptions,
|
|
10
|
+
ObserverOptions,
|
|
11
|
+
ObserverOptionsInternal,
|
|
12
|
+
} from './observerOptions';
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_FORMAT_ERROR = 'invalid';
|
|
6
15
|
|
|
7
|
-
export type
|
|
16
|
+
export type TolgeeStaticData = {
|
|
17
|
+
[key: string]: TreeTranslationsData | (() => Promise<TreeTranslationsData>);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type TolgeeOptionsInternal = {
|
|
8
21
|
/**
|
|
9
22
|
* Initial language
|
|
10
23
|
*/
|
|
11
24
|
language?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tolgee instance url (e.g. https://app.tolgee.io)
|
|
28
|
+
*/
|
|
12
29
|
apiUrl?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Project API key (PAK) or Personal Access Token (PAT)
|
|
33
|
+
*/
|
|
13
34
|
apiKey?: string;
|
|
14
|
-
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Project id is necessary if you are using PAT
|
|
38
|
+
*/
|
|
39
|
+
projectId?: number | string;
|
|
40
|
+
|
|
15
41
|
/**
|
|
16
42
|
* Used when auto detection is not available or is turned off
|
|
17
43
|
*/
|
|
18
44
|
defaultLanguage?: string;
|
|
45
|
+
|
|
19
46
|
/**
|
|
20
47
|
* Languages which can be used for language detection
|
|
21
48
|
* and also limits which values can be stored
|
|
22
49
|
*/
|
|
23
50
|
availableLanguages?: string[];
|
|
51
|
+
|
|
24
52
|
/**
|
|
25
53
|
* Language which is used when no translation is available for current one
|
|
26
54
|
*/
|
|
27
55
|
fallbackLanguage?: FallbackLanguageOption;
|
|
28
|
-
|
|
29
|
-
* Store user language in localStorage (default: true)
|
|
30
|
-
*/
|
|
31
|
-
enableLanguageStore?: boolean;
|
|
56
|
+
|
|
32
57
|
/**
|
|
33
58
|
* Namespaces which should be always fetched
|
|
34
59
|
*/
|
|
35
60
|
ns?: string[];
|
|
61
|
+
|
|
36
62
|
/**
|
|
37
63
|
* Namespaces to be used to find translation when no explicit namespace set.
|
|
38
64
|
*/
|
|
39
|
-
fallbackNs?:
|
|
65
|
+
fallbackNs?: FallbackGeneral;
|
|
66
|
+
|
|
40
67
|
/**
|
|
41
68
|
* Default namespace when no namespace defined (default: '')
|
|
42
69
|
*/
|
|
43
70
|
defaultNs: string;
|
|
71
|
+
|
|
44
72
|
/**
|
|
45
|
-
*
|
|
73
|
+
* These data go directly to cache or you can specify async
|
|
74
|
+
* function which will be used to get the data. Use `:` to add namespace:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* {
|
|
78
|
+
* 'locale': <translations | async function>
|
|
79
|
+
* 'locale:namespace': <translations | async function>
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
46
82
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
83
|
+
staticData?: TolgeeStaticData;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Switches between invisible and text observer. (Default: invisible)
|
|
87
|
+
*/
|
|
88
|
+
observerType: 'invisible' | 'text';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Observer options object.
|
|
92
|
+
*/
|
|
93
|
+
observerOptions: ObserverOptionsInternal;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Define what to display in case of formatting error. (Default: 'invalid')
|
|
97
|
+
*/
|
|
98
|
+
onFormatError: OnFormatError;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type TolgeeOptions = Partial<
|
|
102
|
+
Omit<TolgeeOptionsInternal, 'observerOptions'>
|
|
103
|
+
> & {
|
|
104
|
+
observerOptions?: ObserverOptions;
|
|
51
105
|
};
|
|
52
106
|
|
|
53
107
|
export type State = {
|
|
54
|
-
initialOptions:
|
|
108
|
+
initialOptions: TolgeeOptionsInternal;
|
|
55
109
|
activeNamespaces: Map<string, number>;
|
|
56
110
|
language: string | undefined;
|
|
57
111
|
pendingLanguage: string | undefined;
|
|
@@ -59,24 +113,42 @@ export type State = {
|
|
|
59
113
|
isRunning: boolean;
|
|
60
114
|
};
|
|
61
115
|
|
|
62
|
-
const defaultValues:
|
|
63
|
-
enableLanguageStore: true,
|
|
116
|
+
const defaultValues: TolgeeOptionsInternal = {
|
|
64
117
|
defaultNs: '',
|
|
65
|
-
|
|
118
|
+
observerOptions: defaultObserverOptions,
|
|
119
|
+
observerType: 'invisible',
|
|
120
|
+
onFormatError: DEFAULT_FORMAT_ERROR,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const combineOptions = <T extends TolgeeOptions>(
|
|
124
|
+
...states: (T | undefined)[]
|
|
125
|
+
) => {
|
|
126
|
+
let result = {} as T;
|
|
127
|
+
states.forEach((state) => {
|
|
128
|
+
result = {
|
|
129
|
+
...result,
|
|
130
|
+
...state,
|
|
131
|
+
observerOptions: {
|
|
132
|
+
...result.observerOptions,
|
|
133
|
+
...state?.observerOptions,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
return result;
|
|
66
138
|
};
|
|
67
139
|
|
|
68
140
|
export const initState = (
|
|
69
|
-
options?: Partial<
|
|
141
|
+
options?: Partial<TolgeeOptions>,
|
|
70
142
|
previousState?: State
|
|
71
143
|
): State => {
|
|
72
|
-
const initialOptions =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
144
|
+
const initialOptions = combineOptions(
|
|
145
|
+
defaultValues,
|
|
146
|
+
previousState?.initialOptions,
|
|
147
|
+
options
|
|
148
|
+
) as TolgeeOptionsInternal;
|
|
149
|
+
|
|
77
150
|
// remove extra '/' from url end
|
|
78
|
-
|
|
79
|
-
initialOptions.apiUrl = apiUrl ? apiUrl.replace(/\/+$/, '') : apiUrl;
|
|
151
|
+
initialOptions.apiUrl = sanitizeUrl(initialOptions.apiUrl);
|
|
80
152
|
|
|
81
153
|
return {
|
|
82
154
|
initialOptions,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type ObserverOptionsInternal = {
|
|
2
|
+
/**
|
|
3
|
+
* Attributes that are observed on html elements
|
|
4
|
+
*/
|
|
5
|
+
tagAttributes: Record<string, string[]>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Key(s) which trigger in-context translating (default: ['Alt'])
|
|
9
|
+
*/
|
|
10
|
+
highlightKeys: ModifierKey[];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Color used for key highlighting (default: 'rgb(255, 0, 0)')
|
|
14
|
+
*/
|
|
15
|
+
highlightColor: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Highliter border width (default: 5)
|
|
19
|
+
*/
|
|
20
|
+
highlightWidth: number;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Root element which will be observed (default: document.body)
|
|
24
|
+
*/
|
|
25
|
+
targetElement?: HTMLElement;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Elements which are not observed (default: ['script', 'style'])
|
|
29
|
+
*/
|
|
30
|
+
restrictedElements: string[];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Text observer prefix (default: '%-%tolgee:')
|
|
34
|
+
*/
|
|
35
|
+
inputPrefix: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Text observer suffix (default: '%-%')
|
|
39
|
+
*/
|
|
40
|
+
inputSuffix: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Html elements which will pass click listener to their parent (default: ['option', 'optgroup'])
|
|
44
|
+
*/
|
|
45
|
+
passToParent: (keyof HTMLElementTagNameMap)[] | ((node: Element) => boolean);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ObserverOptions = Partial<ObserverOptionsInternal>;
|
|
49
|
+
|
|
50
|
+
export type ModifierKey = 'Alt' | 'Control' | 'Shift' | 'Meta';
|
|
51
|
+
|
|
52
|
+
export const defaultObserverOptions: ObserverOptionsInternal = {
|
|
53
|
+
tagAttributes: {
|
|
54
|
+
textarea: ['placeholder'],
|
|
55
|
+
input: ['value', 'placeholder'],
|
|
56
|
+
img: ['alt'],
|
|
57
|
+
'*': ['aria-label', 'title'],
|
|
58
|
+
},
|
|
59
|
+
restrictedElements: ['script', 'style'],
|
|
60
|
+
highlightKeys: ['Alt'] as ModifierKey[],
|
|
61
|
+
highlightColor: 'rgb(255, 0, 0)',
|
|
62
|
+
highlightWidth: 5,
|
|
63
|
+
inputPrefix: '%-%tolgee:',
|
|
64
|
+
inputSuffix: '%-%',
|
|
65
|
+
passToParent: ['option', 'optgroup'],
|
|
66
|
+
};
|
|
@@ -2,7 +2,7 @@ export const ValueObserver = <T = any>(
|
|
|
2
2
|
initialValue: T,
|
|
3
3
|
valueGetter: () => T,
|
|
4
4
|
handler: (value: T) => void
|
|
5
|
-
) => {
|
|
5
|
+
): ValueObserverInstance<T> => {
|
|
6
6
|
let previousValue: T = initialValue;
|
|
7
7
|
function init(value: T) {
|
|
8
8
|
previousValue = value;
|
|
@@ -20,4 +20,7 @@ export const ValueObserver = <T = any>(
|
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export type ValueObserverInstance<T> =
|
|
23
|
+
export type ValueObserverInstance<T> = {
|
|
24
|
+
readonly init: (value: T) => void;
|
|
25
|
+
readonly notify: () => void;
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const ERROR_PARAM_EMPTY = 0,
|
|
2
|
+
ERROR_UNEXPECTED_CHAR = 1,
|
|
3
|
+
ERROR_UNEXPECTED_END = 2;
|
|
4
|
+
|
|
5
|
+
export type ErrorCode =
|
|
6
|
+
| typeof ERROR_PARAM_EMPTY
|
|
7
|
+
| typeof ERROR_UNEXPECTED_CHAR
|
|
8
|
+
| typeof ERROR_UNEXPECTED_END;
|
|
9
|
+
|
|
10
|
+
export class FormatError extends Error {
|
|
11
|
+
public readonly code: ErrorCode;
|
|
12
|
+
public readonly index: number;
|
|
13
|
+
constructor(code: ErrorCode, index: number, text: string) {
|
|
14
|
+
let error: string;
|
|
15
|
+
if (code === ERROR_PARAM_EMPTY) {
|
|
16
|
+
error = 'Empty parameter';
|
|
17
|
+
} else if (code === ERROR_UNEXPECTED_CHAR) {
|
|
18
|
+
error = 'Unexpected character';
|
|
19
|
+
} else {
|
|
20
|
+
error = 'Unexpected end';
|
|
21
|
+
}
|
|
22
|
+
super(`Tolgee parser: ${error} at ${index} in "${text}"`);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.index = index;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { formatter } from './formatter';
|
|
2
|
+
import { FinalFormatterMiddleware, TolgeePlugin } from '../types';
|
|
3
|
+
|
|
4
|
+
function createFormatSimple(): FinalFormatterMiddleware {
|
|
5
|
+
return {
|
|
6
|
+
format: ({ translation, params }) => formatter(translation, params),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FormatSimple = (): TolgeePlugin => (tolgee, tools) => {
|
|
11
|
+
tools.setFinalFormatter(createFormatSimple());
|
|
12
|
+
return tolgee;
|
|
13
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorCode,
|
|
3
|
+
ERROR_PARAM_EMPTY,
|
|
4
|
+
ERROR_UNEXPECTED_CHAR,
|
|
5
|
+
ERROR_UNEXPECTED_END,
|
|
6
|
+
FormatError,
|
|
7
|
+
} from './FormatError';
|
|
8
|
+
|
|
9
|
+
function isWhitespace(ch: string) {
|
|
10
|
+
return /\s/.test(ch);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const STATE_TEXT = 0,
|
|
14
|
+
STATE_ESCAPE_MAYBE = 1,
|
|
15
|
+
STATE_ESCAPE = 2,
|
|
16
|
+
STATE_PARAM = 3,
|
|
17
|
+
STATE_PARAM_AFTER = 4;
|
|
18
|
+
|
|
19
|
+
type State =
|
|
20
|
+
| typeof STATE_TEXT
|
|
21
|
+
| typeof STATE_ESCAPE_MAYBE
|
|
22
|
+
| typeof STATE_ESCAPE
|
|
23
|
+
| typeof STATE_PARAM
|
|
24
|
+
| typeof STATE_PARAM_AFTER;
|
|
25
|
+
|
|
26
|
+
const END_STATES = new Set<State>([
|
|
27
|
+
STATE_ESCAPE,
|
|
28
|
+
STATE_ESCAPE_MAYBE,
|
|
29
|
+
STATE_TEXT,
|
|
30
|
+
]);
|
|
31
|
+
const CHAR_ESCAPE = "'";
|
|
32
|
+
const ESCAPABLE = new Set(['{', '}', CHAR_ESCAPE]);
|
|
33
|
+
|
|
34
|
+
const isAllowedInParam = (char: string) => {
|
|
35
|
+
return /[0-9a-zA-Z_]/.test(char);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function formatParser(translation: string) {
|
|
39
|
+
let state: State = STATE_TEXT;
|
|
40
|
+
let text = '';
|
|
41
|
+
let param = '';
|
|
42
|
+
let ch = '';
|
|
43
|
+
const texts: string[] = [];
|
|
44
|
+
const params: string[] = [];
|
|
45
|
+
|
|
46
|
+
let i = 0;
|
|
47
|
+
|
|
48
|
+
function parsingError(code: ErrorCode): never {
|
|
49
|
+
throw new FormatError(code, i, translation);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const addText = () => {
|
|
53
|
+
texts.push(text);
|
|
54
|
+
text = '';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const addParamChar = () => {
|
|
58
|
+
if (!isAllowedInParam(ch)) {
|
|
59
|
+
parsingError(ERROR_UNEXPECTED_CHAR);
|
|
60
|
+
}
|
|
61
|
+
param += ch;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const addParam = () => {
|
|
65
|
+
if (param === '') {
|
|
66
|
+
parsingError(ERROR_PARAM_EMPTY);
|
|
67
|
+
}
|
|
68
|
+
params.push(param);
|
|
69
|
+
param = '';
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
for (i = 0; i < translation.length; i++) {
|
|
73
|
+
ch = translation[i];
|
|
74
|
+
switch (state) {
|
|
75
|
+
case STATE_TEXT:
|
|
76
|
+
if (ch === CHAR_ESCAPE) {
|
|
77
|
+
text += ch;
|
|
78
|
+
state = STATE_ESCAPE_MAYBE;
|
|
79
|
+
} else if (ch === '{') {
|
|
80
|
+
addText();
|
|
81
|
+
state = STATE_PARAM;
|
|
82
|
+
} else {
|
|
83
|
+
text += ch;
|
|
84
|
+
state = STATE_TEXT;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case STATE_ESCAPE_MAYBE:
|
|
89
|
+
if (ESCAPABLE.has(ch)) {
|
|
90
|
+
text = text.slice(0, -1) + ch;
|
|
91
|
+
state = STATE_ESCAPE;
|
|
92
|
+
} else {
|
|
93
|
+
text += ch;
|
|
94
|
+
state = STATE_TEXT;
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case STATE_ESCAPE:
|
|
98
|
+
if (ch === CHAR_ESCAPE) {
|
|
99
|
+
state = STATE_TEXT;
|
|
100
|
+
} else {
|
|
101
|
+
text += ch;
|
|
102
|
+
state = STATE_ESCAPE;
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case STATE_PARAM:
|
|
106
|
+
if (ch === '}') {
|
|
107
|
+
addParam();
|
|
108
|
+
state = STATE_TEXT;
|
|
109
|
+
} else if (!isWhitespace(ch)) {
|
|
110
|
+
addParamChar();
|
|
111
|
+
state = STATE_PARAM;
|
|
112
|
+
} else if (param !== '') {
|
|
113
|
+
addParam();
|
|
114
|
+
state = STATE_PARAM_AFTER;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
break;
|
|
118
|
+
case STATE_PARAM_AFTER:
|
|
119
|
+
if (ch == '}') {
|
|
120
|
+
state = STATE_TEXT;
|
|
121
|
+
} else if (isWhitespace(ch)) {
|
|
122
|
+
state = STATE_PARAM_AFTER;
|
|
123
|
+
} else {
|
|
124
|
+
parsingError(ERROR_UNEXPECTED_CHAR);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!END_STATES.has(state)) {
|
|
129
|
+
parsingError(ERROR_UNEXPECTED_END);
|
|
130
|
+
}
|
|
131
|
+
addText();
|
|
132
|
+
return [texts, params];
|
|
133
|
+
}
|