@tramvai/module-i18n 6.79.10
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 +3 -0
- package/lib/browser.d.ts +10 -0
- package/lib/browser.js +28 -0
- package/lib/server.d.ts +10 -0
- package/lib/server.es.js +28 -0
- package/lib/server.js +41 -0
- package/lib/shared/config.browser.js +10 -0
- package/lib/shared/config.d.ts +3 -0
- package/lib/shared/config.es.js +10 -0
- package/lib/shared/config.js +14 -0
- package/lib/shared/constants.browser.js +9 -0
- package/lib/shared/constants.d.ts +8 -0
- package/lib/shared/constants.es.js +9 -0
- package/lib/shared/constants.js +13 -0
- package/lib/shared/hooks.browser.js +13 -0
- package/lib/shared/hooks.d.ts +3 -0
- package/lib/shared/hooks.es.js +13 -0
- package/lib/shared/hooks.js +17 -0
- package/lib/shared/providers.browser.js +335 -0
- package/lib/shared/providers.d.ts +328 -0
- package/lib/shared/providers.es.js +335 -0
- package/lib/shared/providers.js +339 -0
- package/lib/shared/service.browser.js +164 -0
- package/lib/shared/service.d.ts +32 -0
- package/lib/shared/service.es.js +164 -0
- package/lib/shared/service.js +168 -0
- package/lib/shared/store.browser.js +20 -0
- package/lib/shared/store.d.ts +22 -0
- package/lib/shared/store.es.js +20 -0
- package/lib/shared/store.js +26 -0
- package/lib/tokens.browser.js +9 -0
- package/lib/tokens.d.ts +24 -0
- package/lib/tokens.es.js +9 -0
- package/lib/tokens.js +17 -0
- package/lib/types.d.ts +72 -0
- package/package.json +39 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { parse } from '@tinkoff/url';
|
|
2
|
+
import { RedirectFoundError, makeErrorSilent } from '@tinkoff/errors';
|
|
3
|
+
import { I18nStore } from './store.es.js';
|
|
4
|
+
|
|
5
|
+
class I18nServiceImpl {
|
|
6
|
+
store;
|
|
7
|
+
config;
|
|
8
|
+
cookieManager;
|
|
9
|
+
router;
|
|
10
|
+
requestManager;
|
|
11
|
+
log;
|
|
12
|
+
constructor(deps) {
|
|
13
|
+
this.store = deps.store;
|
|
14
|
+
this.config = deps.config;
|
|
15
|
+
this.cookieManager = deps.cookieManager;
|
|
16
|
+
this.router = deps.router;
|
|
17
|
+
this.requestManager = deps.requestManager;
|
|
18
|
+
this.log = deps.logger('i18n');
|
|
19
|
+
}
|
|
20
|
+
getLanguage() {
|
|
21
|
+
const state = this.store.getState(I18nStore);
|
|
22
|
+
return state.language;
|
|
23
|
+
}
|
|
24
|
+
getAvailableLanguages() {
|
|
25
|
+
const state = this.store.getState(I18nStore);
|
|
26
|
+
return state.availableLanguages;
|
|
27
|
+
}
|
|
28
|
+
// eslint-disable-next-line max-statements
|
|
29
|
+
async switchLanguage(language) {
|
|
30
|
+
const availableLanguages = this.getAvailableLanguages();
|
|
31
|
+
if (!availableLanguages.includes(language)) {
|
|
32
|
+
// TODO: is throwing an error is a best solution?
|
|
33
|
+
throw new Error(`Language "${language}" is not available. Available languages: ${availableLanguages.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
this.log.info(`Switch language to "${language}"`);
|
|
36
|
+
const { cookieName, routingStrategy, updateStrategy } = this.config;
|
|
37
|
+
this.cookieManager.set({
|
|
38
|
+
name: cookieName,
|
|
39
|
+
value: language,
|
|
40
|
+
// TODO valid params!
|
|
41
|
+
});
|
|
42
|
+
const currentUrl = this.router.getCurrentUrl() ?? this.requestManager.getUrl();
|
|
43
|
+
const parsedUrl = typeof currentUrl === 'string' ? parse(currentUrl) : currentUrl;
|
|
44
|
+
const currentLanguage = this.getLanguage();
|
|
45
|
+
const languageFromUrl = this.getLanguageFromUrl(parsedUrl);
|
|
46
|
+
const hasLanguagePrefix = typeof languageFromUrl === 'string';
|
|
47
|
+
const targetUrl = hasLanguagePrefix
|
|
48
|
+
? this.addLanguageToUrl(this.removeLanguageFromUrl(parsedUrl), language)
|
|
49
|
+
: this.addLanguageToUrl(parsedUrl, language);
|
|
50
|
+
const redirectError = new RedirectFoundError({
|
|
51
|
+
nextUrl: `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`,
|
|
52
|
+
httpStatus: 301,
|
|
53
|
+
});
|
|
54
|
+
makeErrorSilent(redirectError);
|
|
55
|
+
/**
|
|
56
|
+
* 'none' - no action needed, language is already set and url is correct
|
|
57
|
+
* 'reload' - url is correct, reload the page to apply language change
|
|
58
|
+
* 'navigate' - need to update url, navigate to the new URL with/without language prefix
|
|
59
|
+
*/
|
|
60
|
+
let action = 'none';
|
|
61
|
+
if (hasLanguagePrefix && languageFromUrl !== language) {
|
|
62
|
+
action = 'navigate';
|
|
63
|
+
}
|
|
64
|
+
else if (currentLanguage !== language) {
|
|
65
|
+
if (routingStrategy === 'no_prefix') {
|
|
66
|
+
action = 'reload';
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
action = 'navigate';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
switch (action) {
|
|
73
|
+
case 'none': {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case 'reload': {
|
|
77
|
+
if (updateStrategy === 'reload') {
|
|
78
|
+
if (typeof window !== 'undefined') {
|
|
79
|
+
window.location.reload();
|
|
80
|
+
return new Promise(() => { });
|
|
81
|
+
}
|
|
82
|
+
throw redirectError;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (typeof window !== 'undefined') {
|
|
86
|
+
return this.router.navigate({
|
|
87
|
+
url: targetUrl.href,
|
|
88
|
+
replace: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
throw redirectError;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
case 'navigate': {
|
|
95
|
+
if (updateStrategy === 'reload') {
|
|
96
|
+
if (typeof window !== 'undefined') {
|
|
97
|
+
window.location.href = targetUrl.href;
|
|
98
|
+
return new Promise(() => { });
|
|
99
|
+
}
|
|
100
|
+
throw redirectError;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
if (typeof window !== 'undefined') {
|
|
104
|
+
return this.router.navigate({
|
|
105
|
+
url: targetUrl.href,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
throw redirectError;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
getLanguageFromUrl(url) {
|
|
114
|
+
const parsedUrl = typeof url === 'string' ? parse(url) : url;
|
|
115
|
+
const { routingStrategy } = this.config;
|
|
116
|
+
const availableLanguages = this.getAvailableLanguages();
|
|
117
|
+
const [firstSegment] = parsedUrl.pathname.split('/').filter(Boolean);
|
|
118
|
+
const isLanguagePrefix = availableLanguages.includes(firstSegment);
|
|
119
|
+
switch (routingStrategy) {
|
|
120
|
+
case 'no_prefix':
|
|
121
|
+
// No prefix strategy, language is not in URL
|
|
122
|
+
return null;
|
|
123
|
+
case 'prefix':
|
|
124
|
+
// Prefix strategy, first segment should be always a language
|
|
125
|
+
return isLanguagePrefix ? firstSegment : null;
|
|
126
|
+
case 'prefix_and_default':
|
|
127
|
+
// Prefix and default strategy, first segment can be a language, or default language if it not matches
|
|
128
|
+
return isLanguagePrefix ? firstSegment : this.config.defaultLanguage;
|
|
129
|
+
case 'prefix_except_default':
|
|
130
|
+
// Prefix except default strategy, first segment can be a language, but can't match default language
|
|
131
|
+
if (isLanguagePrefix) {
|
|
132
|
+
return firstSegment === this.config.defaultLanguage ? null : firstSegment;
|
|
133
|
+
}
|
|
134
|
+
return this.config.defaultLanguage;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
removeLanguageFromUrl(url) {
|
|
138
|
+
const parsedUrl = typeof url === 'string' ? parse(url) : url;
|
|
139
|
+
const languageFromUrl = this.getLanguageFromUrl(parsedUrl);
|
|
140
|
+
const hasLanguagePrefix = typeof languageFromUrl === 'string';
|
|
141
|
+
if (!hasLanguagePrefix) {
|
|
142
|
+
return parsedUrl;
|
|
143
|
+
}
|
|
144
|
+
return parse(`${parsedUrl.origin}${parsedUrl.pathname.replace(`/${languageFromUrl}`, '')}${parsedUrl.search}${parsedUrl.hash}`);
|
|
145
|
+
}
|
|
146
|
+
addLanguageToUrl(url, lang) {
|
|
147
|
+
const { routingStrategy } = this.config;
|
|
148
|
+
const parsedUrl = typeof url === 'string' ? parse(url) : url;
|
|
149
|
+
const languageFromUrl = this.getLanguageFromUrl(parsedUrl);
|
|
150
|
+
const hasLanguagePrefix = typeof languageFromUrl === 'string';
|
|
151
|
+
// If the URL already has a language prefix that matches the requested language, return it as is
|
|
152
|
+
// or if routing strategy is 'no_prefix', return it as is
|
|
153
|
+
// or 'prefix_except_default' or 'prefix_and_default' and the language is default, return it as is
|
|
154
|
+
if ((hasLanguagePrefix && languageFromUrl === lang) ||
|
|
155
|
+
routingStrategy === 'no_prefix' ||
|
|
156
|
+
(routingStrategy === 'prefix_except_default' && lang === this.config.defaultLanguage) ||
|
|
157
|
+
(routingStrategy === 'prefix_and_default' && lang === this.config.defaultLanguage)) {
|
|
158
|
+
return parsedUrl;
|
|
159
|
+
}
|
|
160
|
+
return parse(`${parsedUrl.origin}/${lang}${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export { I18nServiceImpl };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var url = require('@tinkoff/url');
|
|
6
|
+
var errors = require('@tinkoff/errors');
|
|
7
|
+
var store = require('./store.js');
|
|
8
|
+
|
|
9
|
+
class I18nServiceImpl {
|
|
10
|
+
store;
|
|
11
|
+
config;
|
|
12
|
+
cookieManager;
|
|
13
|
+
router;
|
|
14
|
+
requestManager;
|
|
15
|
+
log;
|
|
16
|
+
constructor(deps) {
|
|
17
|
+
this.store = deps.store;
|
|
18
|
+
this.config = deps.config;
|
|
19
|
+
this.cookieManager = deps.cookieManager;
|
|
20
|
+
this.router = deps.router;
|
|
21
|
+
this.requestManager = deps.requestManager;
|
|
22
|
+
this.log = deps.logger('i18n');
|
|
23
|
+
}
|
|
24
|
+
getLanguage() {
|
|
25
|
+
const state = this.store.getState(store.I18nStore);
|
|
26
|
+
return state.language;
|
|
27
|
+
}
|
|
28
|
+
getAvailableLanguages() {
|
|
29
|
+
const state = this.store.getState(store.I18nStore);
|
|
30
|
+
return state.availableLanguages;
|
|
31
|
+
}
|
|
32
|
+
// eslint-disable-next-line max-statements
|
|
33
|
+
async switchLanguage(language) {
|
|
34
|
+
const availableLanguages = this.getAvailableLanguages();
|
|
35
|
+
if (!availableLanguages.includes(language)) {
|
|
36
|
+
// TODO: is throwing an error is a best solution?
|
|
37
|
+
throw new Error(`Language "${language}" is not available. Available languages: ${availableLanguages.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
this.log.info(`Switch language to "${language}"`);
|
|
40
|
+
const { cookieName, routingStrategy, updateStrategy } = this.config;
|
|
41
|
+
this.cookieManager.set({
|
|
42
|
+
name: cookieName,
|
|
43
|
+
value: language,
|
|
44
|
+
// TODO valid params!
|
|
45
|
+
});
|
|
46
|
+
const currentUrl = this.router.getCurrentUrl() ?? this.requestManager.getUrl();
|
|
47
|
+
const parsedUrl = typeof currentUrl === 'string' ? url.parse(currentUrl) : currentUrl;
|
|
48
|
+
const currentLanguage = this.getLanguage();
|
|
49
|
+
const languageFromUrl = this.getLanguageFromUrl(parsedUrl);
|
|
50
|
+
const hasLanguagePrefix = typeof languageFromUrl === 'string';
|
|
51
|
+
const targetUrl = hasLanguagePrefix
|
|
52
|
+
? this.addLanguageToUrl(this.removeLanguageFromUrl(parsedUrl), language)
|
|
53
|
+
: this.addLanguageToUrl(parsedUrl, language);
|
|
54
|
+
const redirectError = new errors.RedirectFoundError({
|
|
55
|
+
nextUrl: `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`,
|
|
56
|
+
httpStatus: 301,
|
|
57
|
+
});
|
|
58
|
+
errors.makeErrorSilent(redirectError);
|
|
59
|
+
/**
|
|
60
|
+
* 'none' - no action needed, language is already set and url is correct
|
|
61
|
+
* 'reload' - url is correct, reload the page to apply language change
|
|
62
|
+
* 'navigate' - need to update url, navigate to the new URL with/without language prefix
|
|
63
|
+
*/
|
|
64
|
+
let action = 'none';
|
|
65
|
+
if (hasLanguagePrefix && languageFromUrl !== language) {
|
|
66
|
+
action = 'navigate';
|
|
67
|
+
}
|
|
68
|
+
else if (currentLanguage !== language) {
|
|
69
|
+
if (routingStrategy === 'no_prefix') {
|
|
70
|
+
action = 'reload';
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
action = 'navigate';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
switch (action) {
|
|
77
|
+
case 'none': {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
case 'reload': {
|
|
81
|
+
if (updateStrategy === 'reload') {
|
|
82
|
+
if (typeof window !== 'undefined') {
|
|
83
|
+
window.location.reload();
|
|
84
|
+
return new Promise(() => { });
|
|
85
|
+
}
|
|
86
|
+
throw redirectError;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (typeof window !== 'undefined') {
|
|
90
|
+
return this.router.navigate({
|
|
91
|
+
url: targetUrl.href,
|
|
92
|
+
replace: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
throw redirectError;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
case 'navigate': {
|
|
99
|
+
if (updateStrategy === 'reload') {
|
|
100
|
+
if (typeof window !== 'undefined') {
|
|
101
|
+
window.location.href = targetUrl.href;
|
|
102
|
+
return new Promise(() => { });
|
|
103
|
+
}
|
|
104
|
+
throw redirectError;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (typeof window !== 'undefined') {
|
|
108
|
+
return this.router.navigate({
|
|
109
|
+
url: targetUrl.href,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
throw redirectError;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
getLanguageFromUrl(url$1) {
|
|
118
|
+
const parsedUrl = typeof url$1 === 'string' ? url.parse(url$1) : url$1;
|
|
119
|
+
const { routingStrategy } = this.config;
|
|
120
|
+
const availableLanguages = this.getAvailableLanguages();
|
|
121
|
+
const [firstSegment] = parsedUrl.pathname.split('/').filter(Boolean);
|
|
122
|
+
const isLanguagePrefix = availableLanguages.includes(firstSegment);
|
|
123
|
+
switch (routingStrategy) {
|
|
124
|
+
case 'no_prefix':
|
|
125
|
+
// No prefix strategy, language is not in URL
|
|
126
|
+
return null;
|
|
127
|
+
case 'prefix':
|
|
128
|
+
// Prefix strategy, first segment should be always a language
|
|
129
|
+
return isLanguagePrefix ? firstSegment : null;
|
|
130
|
+
case 'prefix_and_default':
|
|
131
|
+
// Prefix and default strategy, first segment can be a language, or default language if it not matches
|
|
132
|
+
return isLanguagePrefix ? firstSegment : this.config.defaultLanguage;
|
|
133
|
+
case 'prefix_except_default':
|
|
134
|
+
// Prefix except default strategy, first segment can be a language, but can't match default language
|
|
135
|
+
if (isLanguagePrefix) {
|
|
136
|
+
return firstSegment === this.config.defaultLanguage ? null : firstSegment;
|
|
137
|
+
}
|
|
138
|
+
return this.config.defaultLanguage;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
removeLanguageFromUrl(url$1) {
|
|
142
|
+
const parsedUrl = typeof url$1 === 'string' ? url.parse(url$1) : url$1;
|
|
143
|
+
const languageFromUrl = this.getLanguageFromUrl(parsedUrl);
|
|
144
|
+
const hasLanguagePrefix = typeof languageFromUrl === 'string';
|
|
145
|
+
if (!hasLanguagePrefix) {
|
|
146
|
+
return parsedUrl;
|
|
147
|
+
}
|
|
148
|
+
return url.parse(`${parsedUrl.origin}${parsedUrl.pathname.replace(`/${languageFromUrl}`, '')}${parsedUrl.search}${parsedUrl.hash}`);
|
|
149
|
+
}
|
|
150
|
+
addLanguageToUrl(url$1, lang) {
|
|
151
|
+
const { routingStrategy } = this.config;
|
|
152
|
+
const parsedUrl = typeof url$1 === 'string' ? url.parse(url$1) : url$1;
|
|
153
|
+
const languageFromUrl = this.getLanguageFromUrl(parsedUrl);
|
|
154
|
+
const hasLanguagePrefix = typeof languageFromUrl === 'string';
|
|
155
|
+
// If the URL already has a language prefix that matches the requested language, return it as is
|
|
156
|
+
// or if routing strategy is 'no_prefix', return it as is
|
|
157
|
+
// or 'prefix_except_default' or 'prefix_and_default' and the language is default, return it as is
|
|
158
|
+
if ((hasLanguagePrefix && languageFromUrl === lang) ||
|
|
159
|
+
routingStrategy === 'no_prefix' ||
|
|
160
|
+
(routingStrategy === 'prefix_except_default' && lang === this.config.defaultLanguage) ||
|
|
161
|
+
(routingStrategy === 'prefix_and_default' && lang === this.config.defaultLanguage)) {
|
|
162
|
+
return parsedUrl;
|
|
163
|
+
}
|
|
164
|
+
return url.parse(`${parsedUrl.origin}/${lang}${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
exports.I18nServiceImpl = I18nServiceImpl;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createEvent, createReducer } from '@tramvai/state';
|
|
2
|
+
|
|
3
|
+
const setLanguage = createEvent('i18n:setLanguage');
|
|
4
|
+
const setAvailableLanguages = createEvent('i18n:setAvailableLanguages');
|
|
5
|
+
const initialState = {
|
|
6
|
+
language: 'ru',
|
|
7
|
+
source: 'default',
|
|
8
|
+
availableLanguages: [],
|
|
9
|
+
};
|
|
10
|
+
const I18nStore = createReducer('tramvai_i18n', initialState)
|
|
11
|
+
.on(setLanguage, (state, language) => ({
|
|
12
|
+
...state,
|
|
13
|
+
...language,
|
|
14
|
+
}))
|
|
15
|
+
.on(setAvailableLanguages, (state, availableLanguages) => ({
|
|
16
|
+
...state,
|
|
17
|
+
availableLanguages,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
export { I18nStore, setAvailableLanguages, setLanguage };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Language } from '../types';
|
|
2
|
+
export interface I18nStoreState {
|
|
3
|
+
/**
|
|
4
|
+
* Current active language
|
|
5
|
+
*/
|
|
6
|
+
language: Language;
|
|
7
|
+
/**
|
|
8
|
+
* Current language source
|
|
9
|
+
*/
|
|
10
|
+
source: string;
|
|
11
|
+
/**
|
|
12
|
+
* Available languages whitelist
|
|
13
|
+
*/
|
|
14
|
+
availableLanguages: Language[];
|
|
15
|
+
}
|
|
16
|
+
export declare const setLanguage: import("@tramvai/types-actions-state-context").EventCreator1<{
|
|
17
|
+
language: Language;
|
|
18
|
+
source: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const setAvailableLanguages: import("@tramvai/types-actions-state-context").EventCreator1<string[]>;
|
|
21
|
+
export declare const I18nStore: import("@tramvai/state").Reducer<I18nStoreState, "tramvai_i18n", never>;
|
|
22
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createEvent, createReducer } from '@tramvai/state';
|
|
2
|
+
|
|
3
|
+
const setLanguage = createEvent('i18n:setLanguage');
|
|
4
|
+
const setAvailableLanguages = createEvent('i18n:setAvailableLanguages');
|
|
5
|
+
const initialState = {
|
|
6
|
+
language: 'ru',
|
|
7
|
+
source: 'default',
|
|
8
|
+
availableLanguages: [],
|
|
9
|
+
};
|
|
10
|
+
const I18nStore = createReducer('tramvai_i18n', initialState)
|
|
11
|
+
.on(setLanguage, (state, language) => ({
|
|
12
|
+
...state,
|
|
13
|
+
...language,
|
|
14
|
+
}))
|
|
15
|
+
.on(setAvailableLanguages, (state, availableLanguages) => ({
|
|
16
|
+
...state,
|
|
17
|
+
availableLanguages,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
export { I18nStore, setAvailableLanguages, setLanguage };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var state = require('@tramvai/state');
|
|
6
|
+
|
|
7
|
+
const setLanguage = state.createEvent('i18n:setLanguage');
|
|
8
|
+
const setAvailableLanguages = state.createEvent('i18n:setAvailableLanguages');
|
|
9
|
+
const initialState = {
|
|
10
|
+
language: 'ru',
|
|
11
|
+
source: 'default',
|
|
12
|
+
availableLanguages: [],
|
|
13
|
+
};
|
|
14
|
+
const I18nStore = state.createReducer('tramvai_i18n', initialState)
|
|
15
|
+
.on(setLanguage, (state, language) => ({
|
|
16
|
+
...state,
|
|
17
|
+
...language,
|
|
18
|
+
}))
|
|
19
|
+
.on(setAvailableLanguages, (state, availableLanguages) => ({
|
|
20
|
+
...state,
|
|
21
|
+
availableLanguages,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
exports.I18nStore = I18nStore;
|
|
25
|
+
exports.setAvailableLanguages = setAvailableLanguages;
|
|
26
|
+
exports.setLanguage = setLanguage;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createToken, Scope } from '@tramvai/core';
|
|
2
|
+
|
|
3
|
+
const I18N_CONFIGURATION_TOKEN = createToken('tramvai i18n configuration', { scope: Scope.SINGLETON });
|
|
4
|
+
const I18N_TOKEN = createToken('tramvai i18n service');
|
|
5
|
+
const I18N_RESOLVE_LANGUAGE_TOKEN = createToken('tramvai i18n resolve language');
|
|
6
|
+
const I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN = createToken('tramvai i18n resolve language source', { multi: true });
|
|
7
|
+
const I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN = createToken('tramvai i18n resolve available languages');
|
|
8
|
+
|
|
9
|
+
export { I18N_CONFIGURATION_TOKEN, I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN, I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN, I18N_RESOLVE_LANGUAGE_TOKEN, I18N_TOKEN };
|
package/lib/tokens.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { I18nConfiguration, I18nService, Language } from './types';
|
|
2
|
+
export declare const I18N_CONFIGURATION_TOKEN: I18nConfiguration & {
|
|
3
|
+
__type?: "base token" | undefined;
|
|
4
|
+
};
|
|
5
|
+
export declare const I18N_TOKEN: I18nService & {
|
|
6
|
+
__type?: "base token" | undefined;
|
|
7
|
+
};
|
|
8
|
+
export declare const I18N_RESOLVE_LANGUAGE_TOKEN: (() => Promise<{
|
|
9
|
+
language: Language;
|
|
10
|
+
source: string;
|
|
11
|
+
}>) & {
|
|
12
|
+
__type?: "base token" | undefined;
|
|
13
|
+
};
|
|
14
|
+
export declare const I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN: {
|
|
15
|
+
name: string;
|
|
16
|
+
priority: number;
|
|
17
|
+
resolve: () => Promise<Language | undefined>;
|
|
18
|
+
} & {
|
|
19
|
+
__type?: "multi token" | undefined;
|
|
20
|
+
};
|
|
21
|
+
export declare const I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN: (() => Promise<Language[]>) & {
|
|
22
|
+
__type?: "base token" | undefined;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=tokens.d.ts.map
|
package/lib/tokens.es.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createToken, Scope } from '@tramvai/core';
|
|
2
|
+
|
|
3
|
+
const I18N_CONFIGURATION_TOKEN = createToken('tramvai i18n configuration', { scope: Scope.SINGLETON });
|
|
4
|
+
const I18N_TOKEN = createToken('tramvai i18n service');
|
|
5
|
+
const I18N_RESOLVE_LANGUAGE_TOKEN = createToken('tramvai i18n resolve language');
|
|
6
|
+
const I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN = createToken('tramvai i18n resolve language source', { multi: true });
|
|
7
|
+
const I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN = createToken('tramvai i18n resolve available languages');
|
|
8
|
+
|
|
9
|
+
export { I18N_CONFIGURATION_TOKEN, I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN, I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN, I18N_RESOLVE_LANGUAGE_TOKEN, I18N_TOKEN };
|
package/lib/tokens.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var core = require('@tramvai/core');
|
|
6
|
+
|
|
7
|
+
const I18N_CONFIGURATION_TOKEN = core.createToken('tramvai i18n configuration', { scope: core.Scope.SINGLETON });
|
|
8
|
+
const I18N_TOKEN = core.createToken('tramvai i18n service');
|
|
9
|
+
const I18N_RESOLVE_LANGUAGE_TOKEN = core.createToken('tramvai i18n resolve language');
|
|
10
|
+
const I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN = core.createToken('tramvai i18n resolve language source', { multi: true });
|
|
11
|
+
const I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN = core.createToken('tramvai i18n resolve available languages');
|
|
12
|
+
|
|
13
|
+
exports.I18N_CONFIGURATION_TOKEN = I18N_CONFIGURATION_TOKEN;
|
|
14
|
+
exports.I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN = I18N_RESOLVE_AVAILABLE_LANGUAGES_TOKEN;
|
|
15
|
+
exports.I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN = I18N_RESOLVE_LANGUAGE_SOURCE_TOKEN;
|
|
16
|
+
exports.I18N_RESOLVE_LANGUAGE_TOKEN = I18N_RESOLVE_LANGUAGE_TOKEN;
|
|
17
|
+
exports.I18N_TOKEN = I18N_TOKEN;
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Url } from '@tinkoff/url';
|
|
2
|
+
/**
|
|
3
|
+
* Language code in ISO 639-1 format (e.g., 'en', 'ru', 'zh')
|
|
4
|
+
*/
|
|
5
|
+
export type Language = string;
|
|
6
|
+
/**
|
|
7
|
+
* Routing strategy for internationalization
|
|
8
|
+
* @reference https://i18n.nuxtjs.org/docs/guide#strategies
|
|
9
|
+
*/
|
|
10
|
+
export type I18nRoutingStrategy = 'no_prefix' | 'prefix_except_default' | 'prefix' | 'prefix_and_default';
|
|
11
|
+
/**
|
|
12
|
+
* Strategy for updating the language in the application
|
|
13
|
+
*/
|
|
14
|
+
export type I18nLanguageUpdateStrategy = 'reload' | 'update';
|
|
15
|
+
/**
|
|
16
|
+
* Configuration for the i18n module
|
|
17
|
+
*/
|
|
18
|
+
export interface I18nConfiguration {
|
|
19
|
+
/**
|
|
20
|
+
* Default language to use if language cannot be determined
|
|
21
|
+
* @default 'ru'
|
|
22
|
+
*/
|
|
23
|
+
defaultLanguage: Language;
|
|
24
|
+
/**
|
|
25
|
+
* Routing strategy for handling language in URLs
|
|
26
|
+
* @default 'prefix_except_default'
|
|
27
|
+
*/
|
|
28
|
+
routingStrategy?: I18nRoutingStrategy;
|
|
29
|
+
/**
|
|
30
|
+
* Routing strategy for handling language in URLs
|
|
31
|
+
* @default 'reload'
|
|
32
|
+
*/
|
|
33
|
+
updateStrategy?: I18nLanguageUpdateStrategy;
|
|
34
|
+
/**
|
|
35
|
+
* Static list of available languages whitelist. If not provided, only defaultLanguage will be available.
|
|
36
|
+
* Can be overridden by providing I18N_AVAILABLE_LANGUAGES_TOKEN
|
|
37
|
+
*/
|
|
38
|
+
availableLanguages?: Language[];
|
|
39
|
+
/**
|
|
40
|
+
* Cookie name for storing selected language
|
|
41
|
+
* @default 'tramvai_locale'
|
|
42
|
+
*/
|
|
43
|
+
cookieName?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Command line stage when language and available languages resolution should happen
|
|
46
|
+
* @default 'customerStart'
|
|
47
|
+
*/
|
|
48
|
+
resolveLanguagesCommandLineStage?: 'customerStart' | 'resolveUserDeps';
|
|
49
|
+
/**
|
|
50
|
+
* Whether to enable automatic redirect when detected language doesn't match URL language
|
|
51
|
+
* Only applies to prefix-based strategies
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
enableAutoRedirect?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface I18nService {
|
|
57
|
+
getLanguage(): Language;
|
|
58
|
+
getAvailableLanguages(): Language[];
|
|
59
|
+
/**
|
|
60
|
+
* @description
|
|
61
|
+
* Returns the language from the `url` if available and match current i18n routing strategy, otherwise returns null.
|
|
62
|
+
*/
|
|
63
|
+
getLanguageFromUrl(url: string | Url): Language | null;
|
|
64
|
+
/**
|
|
65
|
+
* @description
|
|
66
|
+
* Navigates to the URL with the specified language prefix and saves the new language to the store and locale cookie
|
|
67
|
+
*/
|
|
68
|
+
switchLanguage(language: Language): Promise<void>;
|
|
69
|
+
removeLanguageFromUrl(url: string | Url): Url;
|
|
70
|
+
addLanguageToUrl(url: string | Url, language: Language): Url;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tramvai/module-i18n",
|
|
3
|
+
"version": "6.79.10",
|
|
4
|
+
"description": "Internationalization (i18n) module for Tramvai",
|
|
5
|
+
"main": "lib/server.js",
|
|
6
|
+
"module": "lib/server.es.js",
|
|
7
|
+
"browser": "lib/browser.js",
|
|
8
|
+
"typings": "lib/server.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"lib"
|
|
11
|
+
],
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+ssh://git@github.com/tramvaijs/tramvai.git"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tramvai-build --forPublish --preserveModules",
|
|
19
|
+
"watch": "tsc -w"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@tinkoff/errors": "0.7.3",
|
|
23
|
+
"@tinkoff/url": "0.12.3",
|
|
24
|
+
"@tramvai/module-client-hints": "6.79.10",
|
|
25
|
+
"@tramvai/tokens-common": "6.79.10",
|
|
26
|
+
"@tramvai/tokens-cookie": "6.79.10",
|
|
27
|
+
"@tramvai/tokens-render": "6.79.10",
|
|
28
|
+
"@tramvai/tokens-router": "6.79.10"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@tinkoff/dippy": "0.12.7",
|
|
32
|
+
"@tinkoff/logger": "^0.10.507",
|
|
33
|
+
"@tramvai/core": "6.79.10",
|
|
34
|
+
"@tramvai/react": "6.79.10",
|
|
35
|
+
"@tramvai/state": "6.79.10",
|
|
36
|
+
"tslib": "^2.4.0"
|
|
37
|
+
},
|
|
38
|
+
"license": "Apache-2.0"
|
|
39
|
+
}
|