@jetshop/intl 5.13.2 → 5.13.10-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/babel.js +23 -23
- package/bin/extract.js +21 -21
- package/bin/intl.js +34 -34
- package/bin/lint.js +87 -87
- package/config.js +119 -119
- package/context.ts +5 -5
- package/index.mdx +101 -101
- package/index.ts +66 -66
- package/package.json +1 -1
- package/tooling.mdx +33 -33
- package/tsconfig.json +8 -8
package/babel.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
|
|
2
|
-
if (env !== 'development' && env !== 'test' && env !== 'production') {
|
|
3
|
-
throw new Error(
|
|
4
|
-
'Using `babel-preset-jetshop-intl` requires that you specify `NODE_ENV` ' +
|
|
5
|
-
'or `BABEL_ENV` environment variables. Valid values are "development", ' +
|
|
6
|
-
'"test", and "production". Instead, received: ' +
|
|
7
|
-
JSON.stringify(env) +
|
|
8
|
-
'.'
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
module.exports = function(context, opts) {
|
|
13
|
-
return {
|
|
14
|
-
plugins: [
|
|
15
|
-
[
|
|
16
|
-
require.resolve('babel-plugin-transform-format-message'),
|
|
17
|
-
{
|
|
18
|
-
generateId: 'underscored_crc32'
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
]
|
|
22
|
-
};
|
|
23
|
-
};
|
|
1
|
+
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
|
|
2
|
+
if (env !== 'development' && env !== 'test' && env !== 'production') {
|
|
3
|
+
throw new Error(
|
|
4
|
+
'Using `babel-preset-jetshop-intl` requires that you specify `NODE_ENV` ' +
|
|
5
|
+
'or `BABEL_ENV` environment variables. Valid values are "development", ' +
|
|
6
|
+
'"test", and "production". Instead, received: ' +
|
|
7
|
+
JSON.stringify(env) +
|
|
8
|
+
'.'
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = function(context, opts) {
|
|
13
|
+
return {
|
|
14
|
+
plugins: [
|
|
15
|
+
[
|
|
16
|
+
require.resolve('babel-plugin-transform-format-message'),
|
|
17
|
+
{
|
|
18
|
+
generateId: 'underscored_crc32'
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
};
|
package/bin/extract.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
-
|
|
4
|
-
const program = require('format-message-cli');
|
|
5
|
-
// TODO: Support typescript
|
|
6
|
-
program.parse(
|
|
7
|
-
process.argv
|
|
8
|
-
.slice(0, 2)
|
|
9
|
-
.concat([
|
|
10
|
-
'extract',
|
|
11
|
-
'src/**/*.js',
|
|
12
|
-
'node_modules/@jetshop/ui/**/*.js',
|
|
13
|
-
'node_modules/@jetshop/core/**/*.js',
|
|
14
|
-
'../ui/**/*.js',
|
|
15
|
-
'../core/**/*.js',
|
|
16
|
-
'-g',
|
|
17
|
-
'underscored_crc32',
|
|
18
|
-
'-o',
|
|
19
|
-
'translations/default.json'
|
|
20
|
-
])
|
|
21
|
-
);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
+
|
|
4
|
+
const program = require('format-message-cli');
|
|
5
|
+
// TODO: Support typescript
|
|
6
|
+
program.parse(
|
|
7
|
+
process.argv
|
|
8
|
+
.slice(0, 2)
|
|
9
|
+
.concat([
|
|
10
|
+
'extract',
|
|
11
|
+
'src/**/*.js',
|
|
12
|
+
'node_modules/@jetshop/ui/**/*.js',
|
|
13
|
+
'node_modules/@jetshop/core/**/*.js',
|
|
14
|
+
'../ui/**/*.js',
|
|
15
|
+
'../core/**/*.js',
|
|
16
|
+
'-g',
|
|
17
|
+
'underscored_crc32',
|
|
18
|
+
'-o',
|
|
19
|
+
'translations/default.json'
|
|
20
|
+
])
|
|
21
|
+
);
|
package/bin/intl.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
-
|
|
4
|
-
process.env.NODE_ENV = 'production';
|
|
5
|
-
process.env.MODULE_NAME_PATTERN = '@jetshop/intl';
|
|
6
|
-
|
|
7
|
-
require('@jetshop/react-scripts/config/env');
|
|
8
|
-
const spawn = require('react-dev-utils/crossSpawn');
|
|
9
|
-
const args = process.argv.slice(2);
|
|
10
|
-
|
|
11
|
-
const scriptIndex = args.findIndex(x => x === 'extract' || x === 'lint');
|
|
12
|
-
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
|
|
13
|
-
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
|
|
14
|
-
|
|
15
|
-
switch (script) {
|
|
16
|
-
case 'extract':
|
|
17
|
-
case 'lint': {
|
|
18
|
-
const result = spawn.sync(
|
|
19
|
-
'node',
|
|
20
|
-
nodeArgs
|
|
21
|
-
.concat(require.resolve('./' + script))
|
|
22
|
-
.concat(args.slice(scriptIndex + 1)),
|
|
23
|
-
{ stdio: 'inherit' }
|
|
24
|
-
);
|
|
25
|
-
if (result.signal) {
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
process.exit(result.status);
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
default:
|
|
32
|
-
console.log('Unknown script "' + script + '".');
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
+
|
|
4
|
+
process.env.NODE_ENV = 'production';
|
|
5
|
+
process.env.MODULE_NAME_PATTERN = '@jetshop/intl';
|
|
6
|
+
|
|
7
|
+
require('@jetshop/react-scripts/config/env');
|
|
8
|
+
const spawn = require('react-dev-utils/crossSpawn');
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
const scriptIndex = args.findIndex(x => x === 'extract' || x === 'lint');
|
|
12
|
+
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
|
|
13
|
+
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
|
|
14
|
+
|
|
15
|
+
switch (script) {
|
|
16
|
+
case 'extract':
|
|
17
|
+
case 'lint': {
|
|
18
|
+
const result = spawn.sync(
|
|
19
|
+
'node',
|
|
20
|
+
nodeArgs
|
|
21
|
+
.concat(require.resolve('./' + script))
|
|
22
|
+
.concat(args.slice(scriptIndex + 1)),
|
|
23
|
+
{ stdio: 'inherit' }
|
|
24
|
+
);
|
|
25
|
+
if (result.signal) {
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
process.exit(result.status);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
default:
|
|
32
|
+
console.log('Unknown script "' + script + '".');
|
|
33
|
+
break;
|
|
34
|
+
}
|
package/bin/lint.js
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const glob = require('glob');
|
|
6
|
-
const CLIEngine = require('eslint').CLIEngine;
|
|
7
|
-
const {
|
|
8
|
-
uiPath,
|
|
9
|
-
corePath,
|
|
10
|
-
intlPath
|
|
11
|
-
} = require('@jetshop/react-scripts/config/paths');
|
|
12
|
-
|
|
13
|
-
const translationFiles = glob.sync('translations/*.json');
|
|
14
|
-
const srcFiles = [
|
|
15
|
-
...glob.sync('src/**/*.{js,ts,tsx}'),
|
|
16
|
-
...glob.sync(`${uiPath}/**/*.{js,ts,tsx}`),
|
|
17
|
-
...glob.sync(`${corePath}/**/*.{js,ts,tsx}`)
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const localeRegexp = /([a-z-_A-Z]+)\.json/;
|
|
21
|
-
|
|
22
|
-
const getLocaleFromFilename = filename => {
|
|
23
|
-
if (localeRegexp.test(filename)) {
|
|
24
|
-
return filename.match(localeRegexp)[1];
|
|
25
|
-
} else {
|
|
26
|
-
throw new Error('Could not figure out locale for ' + filename);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const translations = translationFiles.reduce((acc, file) => {
|
|
31
|
-
const locale = getLocaleFromFilename(file);
|
|
32
|
-
|
|
33
|
-
// Skip linting the default.json file
|
|
34
|
-
if (locale === 'default') {
|
|
35
|
-
return acc;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
39
|
-
return {
|
|
40
|
-
...acc,
|
|
41
|
-
[locale]: data
|
|
42
|
-
};
|
|
43
|
-
}, {});
|
|
44
|
-
|
|
45
|
-
var cli = new CLIEngine({
|
|
46
|
-
useEslintrc: false,
|
|
47
|
-
ignorePath: `${intlPath}/bin/.eslintignore`,
|
|
48
|
-
envs: ['es6', 'browser', 'node'],
|
|
49
|
-
baseConfig: {
|
|
50
|
-
parser: '@typescript-eslint/parser',
|
|
51
|
-
parserOptions: {
|
|
52
|
-
ecmaVersion: 6,
|
|
53
|
-
sourceType: 'module',
|
|
54
|
-
ecmaFeatures: {
|
|
55
|
-
modules: true,
|
|
56
|
-
experimentalObjectRestSpread: true,
|
|
57
|
-
jsx: true
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
plugins: ['format-message', '@typescript-eslint'],
|
|
61
|
-
settings: {
|
|
62
|
-
'format-message': {
|
|
63
|
-
generateId: 'underscored_crc32',
|
|
64
|
-
translations
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
extends: 'plugin:format-message/default'
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const report = {
|
|
72
|
-
results: [],
|
|
73
|
-
errorCount: 0,
|
|
74
|
-
warningCount: 0
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
srcFiles.forEach(file => {
|
|
78
|
-
const data = fs.readFileSync(file, 'utf8');
|
|
79
|
-
const fileReport = cli.executeOnText(data, file);
|
|
80
|
-
report.results = report.results.concat(fileReport.results);
|
|
81
|
-
report.errorCount += fileReport.errorCount;
|
|
82
|
-
report.warningCount += fileReport.warningCount;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const format = cli.getFormatter('stylish');
|
|
86
|
-
const formatted = format(report.results);
|
|
87
|
-
if (formatted) console.error(formatted);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const glob = require('glob');
|
|
6
|
+
const CLIEngine = require('eslint').CLIEngine;
|
|
7
|
+
const {
|
|
8
|
+
uiPath,
|
|
9
|
+
corePath,
|
|
10
|
+
intlPath
|
|
11
|
+
} = require('@jetshop/react-scripts/config/paths');
|
|
12
|
+
|
|
13
|
+
const translationFiles = glob.sync('translations/*.json');
|
|
14
|
+
const srcFiles = [
|
|
15
|
+
...glob.sync('src/**/*.{js,ts,tsx}'),
|
|
16
|
+
...glob.sync(`${uiPath}/**/*.{js,ts,tsx}`),
|
|
17
|
+
...glob.sync(`${corePath}/**/*.{js,ts,tsx}`)
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const localeRegexp = /([a-z-_A-Z]+)\.json/;
|
|
21
|
+
|
|
22
|
+
const getLocaleFromFilename = filename => {
|
|
23
|
+
if (localeRegexp.test(filename)) {
|
|
24
|
+
return filename.match(localeRegexp)[1];
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error('Could not figure out locale for ' + filename);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const translations = translationFiles.reduce((acc, file) => {
|
|
31
|
+
const locale = getLocaleFromFilename(file);
|
|
32
|
+
|
|
33
|
+
// Skip linting the default.json file
|
|
34
|
+
if (locale === 'default') {
|
|
35
|
+
return acc;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
39
|
+
return {
|
|
40
|
+
...acc,
|
|
41
|
+
[locale]: data
|
|
42
|
+
};
|
|
43
|
+
}, {});
|
|
44
|
+
|
|
45
|
+
var cli = new CLIEngine({
|
|
46
|
+
useEslintrc: false,
|
|
47
|
+
ignorePath: `${intlPath}/bin/.eslintignore`,
|
|
48
|
+
envs: ['es6', 'browser', 'node'],
|
|
49
|
+
baseConfig: {
|
|
50
|
+
parser: '@typescript-eslint/parser',
|
|
51
|
+
parserOptions: {
|
|
52
|
+
ecmaVersion: 6,
|
|
53
|
+
sourceType: 'module',
|
|
54
|
+
ecmaFeatures: {
|
|
55
|
+
modules: true,
|
|
56
|
+
experimentalObjectRestSpread: true,
|
|
57
|
+
jsx: true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
plugins: ['format-message', '@typescript-eslint'],
|
|
61
|
+
settings: {
|
|
62
|
+
'format-message': {
|
|
63
|
+
generateId: 'underscored_crc32',
|
|
64
|
+
translations
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
extends: 'plugin:format-message/default'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const report = {
|
|
72
|
+
results: [],
|
|
73
|
+
errorCount: 0,
|
|
74
|
+
warningCount: 0
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
srcFiles.forEach(file => {
|
|
78
|
+
const data = fs.readFileSync(file, 'utf8');
|
|
79
|
+
const fileReport = cli.executeOnText(data, file);
|
|
80
|
+
report.results = report.results.concat(fileReport.results);
|
|
81
|
+
report.errorCount += fileReport.errorCount;
|
|
82
|
+
report.warningCount += fileReport.warningCount;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const format = cli.getFormatter('stylish');
|
|
86
|
+
const formatted = format(report.results);
|
|
87
|
+
if (formatted) console.error(formatted);
|
package/config.js
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import formatMessage from 'format-message';
|
|
2
|
-
import underscoredCrc32 from 'format-message-generate-id/underscored_crc32';
|
|
3
|
-
|
|
4
|
-
// Cache translation files
|
|
5
|
-
const cachedTranslationPromises = {};
|
|
6
|
-
|
|
7
|
-
const createTranslator = ({
|
|
8
|
-
locale,
|
|
9
|
-
fallback,
|
|
10
|
-
messages,
|
|
11
|
-
fallbackMessages,
|
|
12
|
-
options
|
|
13
|
-
}) => {
|
|
14
|
-
const translator = __IN_SERVER__ ? formatMessage.namespace() : formatMessage;
|
|
15
|
-
|
|
16
|
-
translator.setup({
|
|
17
|
-
translations: {
|
|
18
|
-
[locale]: messages,
|
|
19
|
-
[fallback]: fallbackMessages
|
|
20
|
-
},
|
|
21
|
-
locale: [locale, fallback],
|
|
22
|
-
generateId: underscoredCrc32,
|
|
23
|
-
...options
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return translator;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Takes a locale, e.g. 'sv-SE' and resolves it to the corresponding key in the translations
|
|
31
|
-
* @param {string} locale Example: `sv-SE`
|
|
32
|
-
* @param {object} translations
|
|
33
|
-
* @return {string} Example: `sv`
|
|
34
|
-
*/
|
|
35
|
-
export const getLocaleName = (locale, translations) => {
|
|
36
|
-
if (!locale) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (locale in translations) {
|
|
41
|
-
return locale;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const lowerCase = locale.toLowerCase();
|
|
45
|
-
if (lowerCase in translations) {
|
|
46
|
-
return lowerCase;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const baseLocale = lowerCase.split(/[_-]/)[0];
|
|
50
|
-
if (baseLocale in translations) {
|
|
51
|
-
return baseLocale;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return null;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const getLocaleWithFallback = ({
|
|
58
|
-
locale,
|
|
59
|
-
defaultLocale,
|
|
60
|
-
translations
|
|
61
|
-
}) => {
|
|
62
|
-
const fallback = getLocaleName(defaultLocale, translations);
|
|
63
|
-
const loc = getLocaleName(locale, translations);
|
|
64
|
-
|
|
65
|
-
if (!fallback) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`Default locale "${defaultLocale}" could not be found in translations`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
locale: loc || fallback,
|
|
72
|
-
fallback
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const loadLocaleMessages = (locale, translations) => {
|
|
77
|
-
if (!__IN_SERVER__) {
|
|
78
|
-
return translations[locale]();
|
|
79
|
-
}
|
|
80
|
-
if (!cachedTranslationPromises[locale]) {
|
|
81
|
-
cachedTranslationPromises[locale] = translations[locale]();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return cachedTranslationPromises[locale];
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
export const setupIntl = async config => {
|
|
88
|
-
if (!config.translations) {
|
|
89
|
-
throw new Error('No translation data provided to translation setup.');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!config.defaultLocale) {
|
|
93
|
-
throw new Error('No default locale provided to translation setup.');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const translations = config.translations;
|
|
97
|
-
const options = config.options || {};
|
|
98
|
-
|
|
99
|
-
const { locale, fallback } = getLocaleWithFallback({
|
|
100
|
-
locale: config.locale,
|
|
101
|
-
defaultLocale: config.defaultLocale,
|
|
102
|
-
translations
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const [messages, fallbackMessages] = await Promise.all([
|
|
106
|
-
loadLocaleMessages(locale, translations),
|
|
107
|
-
loadLocaleMessages(fallback, translations)
|
|
108
|
-
]);
|
|
109
|
-
|
|
110
|
-
const translator = createTranslator({
|
|
111
|
-
locale,
|
|
112
|
-
fallback,
|
|
113
|
-
messages,
|
|
114
|
-
fallbackMessages,
|
|
115
|
-
options
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
return { locale, translator, fallback };
|
|
119
|
-
};
|
|
1
|
+
import formatMessage from 'format-message';
|
|
2
|
+
import underscoredCrc32 from 'format-message-generate-id/underscored_crc32';
|
|
3
|
+
|
|
4
|
+
// Cache translation files
|
|
5
|
+
const cachedTranslationPromises = {};
|
|
6
|
+
|
|
7
|
+
const createTranslator = ({
|
|
8
|
+
locale,
|
|
9
|
+
fallback,
|
|
10
|
+
messages,
|
|
11
|
+
fallbackMessages,
|
|
12
|
+
options
|
|
13
|
+
}) => {
|
|
14
|
+
const translator = __IN_SERVER__ ? formatMessage.namespace() : formatMessage;
|
|
15
|
+
|
|
16
|
+
translator.setup({
|
|
17
|
+
translations: {
|
|
18
|
+
[locale]: messages,
|
|
19
|
+
[fallback]: fallbackMessages
|
|
20
|
+
},
|
|
21
|
+
locale: [locale, fallback],
|
|
22
|
+
generateId: underscoredCrc32,
|
|
23
|
+
...options
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return translator;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Takes a locale, e.g. 'sv-SE' and resolves it to the corresponding key in the translations
|
|
31
|
+
* @param {string} locale Example: `sv-SE`
|
|
32
|
+
* @param {object} translations
|
|
33
|
+
* @return {string} Example: `sv`
|
|
34
|
+
*/
|
|
35
|
+
export const getLocaleName = (locale, translations) => {
|
|
36
|
+
if (!locale) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (locale in translations) {
|
|
41
|
+
return locale;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lowerCase = locale.toLowerCase();
|
|
45
|
+
if (lowerCase in translations) {
|
|
46
|
+
return lowerCase;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const baseLocale = lowerCase.split(/[_-]/)[0];
|
|
50
|
+
if (baseLocale in translations) {
|
|
51
|
+
return baseLocale;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getLocaleWithFallback = ({
|
|
58
|
+
locale,
|
|
59
|
+
defaultLocale,
|
|
60
|
+
translations
|
|
61
|
+
}) => {
|
|
62
|
+
const fallback = getLocaleName(defaultLocale, translations);
|
|
63
|
+
const loc = getLocaleName(locale, translations);
|
|
64
|
+
|
|
65
|
+
if (!fallback) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Default locale "${defaultLocale}" could not be found in translations`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
locale: loc || fallback,
|
|
72
|
+
fallback
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const loadLocaleMessages = (locale, translations) => {
|
|
77
|
+
if (!__IN_SERVER__) {
|
|
78
|
+
return translations[locale]();
|
|
79
|
+
}
|
|
80
|
+
if (!cachedTranslationPromises[locale]) {
|
|
81
|
+
cachedTranslationPromises[locale] = translations[locale]();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return cachedTranslationPromises[locale];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const setupIntl = async config => {
|
|
88
|
+
if (!config.translations) {
|
|
89
|
+
throw new Error('No translation data provided to translation setup.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!config.defaultLocale) {
|
|
93
|
+
throw new Error('No default locale provided to translation setup.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const translations = config.translations;
|
|
97
|
+
const options = config.options || {};
|
|
98
|
+
|
|
99
|
+
const { locale, fallback } = getLocaleWithFallback({
|
|
100
|
+
locale: config.locale,
|
|
101
|
+
defaultLocale: config.defaultLocale,
|
|
102
|
+
translations
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const [messages, fallbackMessages] = await Promise.all([
|
|
106
|
+
loadLocaleMessages(locale, translations),
|
|
107
|
+
loadLocaleMessages(fallback, translations)
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const translator = createTranslator({
|
|
111
|
+
locale,
|
|
112
|
+
fallback,
|
|
113
|
+
messages,
|
|
114
|
+
fallbackMessages,
|
|
115
|
+
options
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return { locale, translator, fallback };
|
|
119
|
+
};
|
package/context.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createContext } from 'react';
|
|
2
|
-
import formatMessage from 'format-message';
|
|
3
|
-
|
|
4
|
-
const IntlContext = createContext(formatMessage);
|
|
5
|
-
export default IntlContext;
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import formatMessage from 'format-message';
|
|
3
|
+
|
|
4
|
+
const IntlContext = createContext(formatMessage);
|
|
5
|
+
export default IntlContext;
|
package/index.mdx
CHANGED
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Introduction
|
|
3
|
-
menu: '@jetshop/intl'
|
|
4
|
-
route: /intl/introduction
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Introduction
|
|
8
|
-
|
|
9
|
-
Localization and translations are essential for building a good cross-border e-commerce, and in Flight there's some tools to help you with that.
|
|
10
|
-
|
|
11
|
-
## Translations
|
|
12
|
-
|
|
13
|
-
Much of the content in Flight stores come from the API, which already handles translations. However, there's many cases where you want to keep the texts inside the shop repository, or where you want to do some kind of formatting before rendering the text.
|
|
14
|
-
For this we have a wrapper around the library [format-message](https://github.com/format-message/format-message). The wrapper around format-message is needed to use the correct translations when doing server-side rendering.
|
|
15
|
-
|
|
16
|
-
When you want to render a translation string you should preferrably use the `useIntl` hook to wrap your translation strings:
|
|
17
|
-
|
|
18
|
-
```javascript
|
|
19
|
-
import { useIntl } from '@jetshop/intl';
|
|
20
|
-
|
|
21
|
-
const App = () => {
|
|
22
|
-
const t = useIntl();
|
|
23
|
-
|
|
24
|
-
return <button>{t('This is my translatable button text')}</button>;
|
|
25
|
-
};
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
This will result in a pure text string rendered where you call the function. There are lots of places in Flight where we use an older way of rendering translation strings, importing `t` directly from `@jetshop/intl`.
|
|
29
|
-
This works, but is not recommended, since it has some special behaviour that might cause issues in some places.
|
|
30
|
-
|
|
31
|
-
```javascript
|
|
32
|
-
import t from '@jetshop/intl';
|
|
33
|
-
|
|
34
|
-
const App = () => {
|
|
35
|
-
return <button>{t('This is my translatable button text')}</button>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// what is actually being rendered
|
|
39
|
-
const App = () => {
|
|
40
|
-
return <button><IntlContext.Consumer>{t => t('This is my translatable button text')}</IntlContext></button>
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
In most cases this works well, but if you try to use a translation string inside e.g. a input placeholder, you will end up with broken markup.
|
|
45
|
-
|
|
46
|
-
You can also work with inline placeholders:
|
|
47
|
-
|
|
48
|
-
```javascript
|
|
49
|
-
import { useIntl } from '@jetshop/intl';
|
|
50
|
-
|
|
51
|
-
const App = () => {
|
|
52
|
-
const t = useIntl();
|
|
53
|
-
return <span>{t('Hi {name}! How are you doing?', { name: 'User' })}</span>;
|
|
54
|
-
};
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The [ICU Message Format](http://userguide.icu-project.org/formatparse/messages) that format-message uses, also support pluralization:
|
|
58
|
-
|
|
59
|
-
```javascript
|
|
60
|
-
import { useIntl } from '@jetshop/intl';
|
|
61
|
-
|
|
62
|
-
const App = () => {
|
|
63
|
-
const t = useIntl();
|
|
64
|
-
const [count, setCount] = useState(0);
|
|
65
|
-
return (
|
|
66
|
-
<button onClick={() => setCount(count + 1)}>
|
|
67
|
-
{t(
|
|
68
|
-
`{count, plural,
|
|
69
|
-
=0 {You have not pressed the button}
|
|
70
|
-
one {You have pressed the button one time}
|
|
71
|
-
other {You have pressed the button # times}
|
|
72
|
-
}`,
|
|
73
|
-
{ count }
|
|
74
|
-
)}
|
|
75
|
-
</button>
|
|
76
|
-
);
|
|
77
|
-
};
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Finally it's also possible to render your own components for inline styling using `t.rich`:
|
|
81
|
-
|
|
82
|
-
```javascript
|
|
83
|
-
import t from '@jetshop/intl';
|
|
84
|
-
|
|
85
|
-
const Important = ({ children }) => <strong key="important">{children}</strong>;
|
|
86
|
-
|
|
87
|
-
const App = () => (
|
|
88
|
-
<span>
|
|
89
|
-
{t.rich('This text is <Important>very important!</Important>', {
|
|
90
|
-
Important
|
|
91
|
-
})}
|
|
92
|
-
</span>
|
|
93
|
-
);
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Note that this does not work with the `useIntl` import.
|
|
97
|
-
|
|
98
|
-
## Localization
|
|
99
|
-
|
|
100
|
-
format-message can also help with formatting text according to local standards, such as date, time, numbers etc. It's basically a wrapper around the built in Intl functions in the browser and can be used either together with translation strings as a part of the ICU message format, or by using the exposed helper functions on its own.
|
|
101
|
-
See [these examples](https://github.com/format-message/format-message/tree/master/packages/format-message#complex-number-date-and-time-placeholders) on the format-message documentation.
|
|
1
|
+
---
|
|
2
|
+
name: Introduction
|
|
3
|
+
menu: '@jetshop/intl'
|
|
4
|
+
route: /intl/introduction
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Introduction
|
|
8
|
+
|
|
9
|
+
Localization and translations are essential for building a good cross-border e-commerce, and in Flight there's some tools to help you with that.
|
|
10
|
+
|
|
11
|
+
## Translations
|
|
12
|
+
|
|
13
|
+
Much of the content in Flight stores come from the API, which already handles translations. However, there's many cases where you want to keep the texts inside the shop repository, or where you want to do some kind of formatting before rendering the text.
|
|
14
|
+
For this we have a wrapper around the library [format-message](https://github.com/format-message/format-message). The wrapper around format-message is needed to use the correct translations when doing server-side rendering.
|
|
15
|
+
|
|
16
|
+
When you want to render a translation string you should preferrably use the `useIntl` hook to wrap your translation strings:
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import { useIntl } from '@jetshop/intl';
|
|
20
|
+
|
|
21
|
+
const App = () => {
|
|
22
|
+
const t = useIntl();
|
|
23
|
+
|
|
24
|
+
return <button>{t('This is my translatable button text')}</button>;
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This will result in a pure text string rendered where you call the function. There are lots of places in Flight where we use an older way of rendering translation strings, importing `t` directly from `@jetshop/intl`.
|
|
29
|
+
This works, but is not recommended, since it has some special behaviour that might cause issues in some places.
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
import t from '@jetshop/intl';
|
|
33
|
+
|
|
34
|
+
const App = () => {
|
|
35
|
+
return <button>{t('This is my translatable button text')}</button>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// what is actually being rendered
|
|
39
|
+
const App = () => {
|
|
40
|
+
return <button><IntlContext.Consumer>{t => t('This is my translatable button text')}</IntlContext></button>
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
In most cases this works well, but if you try to use a translation string inside e.g. a input placeholder, you will end up with broken markup.
|
|
45
|
+
|
|
46
|
+
You can also work with inline placeholders:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import { useIntl } from '@jetshop/intl';
|
|
50
|
+
|
|
51
|
+
const App = () => {
|
|
52
|
+
const t = useIntl();
|
|
53
|
+
return <span>{t('Hi {name}! How are you doing?', { name: 'User' })}</span>;
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The [ICU Message Format](http://userguide.icu-project.org/formatparse/messages) that format-message uses, also support pluralization:
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
import { useIntl } from '@jetshop/intl';
|
|
61
|
+
|
|
62
|
+
const App = () => {
|
|
63
|
+
const t = useIntl();
|
|
64
|
+
const [count, setCount] = useState(0);
|
|
65
|
+
return (
|
|
66
|
+
<button onClick={() => setCount(count + 1)}>
|
|
67
|
+
{t(
|
|
68
|
+
`{count, plural,
|
|
69
|
+
=0 {You have not pressed the button}
|
|
70
|
+
one {You have pressed the button one time}
|
|
71
|
+
other {You have pressed the button # times}
|
|
72
|
+
}`,
|
|
73
|
+
{ count }
|
|
74
|
+
)}
|
|
75
|
+
</button>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Finally it's also possible to render your own components for inline styling using `t.rich`:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
import t from '@jetshop/intl';
|
|
84
|
+
|
|
85
|
+
const Important = ({ children }) => <strong key="important">{children}</strong>;
|
|
86
|
+
|
|
87
|
+
const App = () => (
|
|
88
|
+
<span>
|
|
89
|
+
{t.rich('This text is <Important>very important!</Important>', {
|
|
90
|
+
Important
|
|
91
|
+
})}
|
|
92
|
+
</span>
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Note that this does not work with the `useIntl` import.
|
|
97
|
+
|
|
98
|
+
## Localization
|
|
99
|
+
|
|
100
|
+
format-message can also help with formatting text according to local standards, such as date, time, numbers etc. It's basically a wrapper around the built in Intl functions in the browser and can be used either together with translation strings as a part of the ICU message format, or by using the exposed helper functions on its own.
|
|
101
|
+
See [these examples](https://github.com/format-message/format-message/tree/master/packages/format-message#complex-number-date-and-time-placeholders) on the format-message documentation.
|
package/index.ts
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
2
|
-
import IntlContext from '@jetshop/intl/context';
|
|
3
|
-
import formatMessage from 'format-message/types';
|
|
4
|
-
|
|
5
|
-
export const useIntl = () => useContext(IntlContext);
|
|
6
|
-
|
|
7
|
-
type FormatMsgType = typeof formatMessage;
|
|
8
|
-
type FormatMsgTypes =
|
|
9
|
-
| FormatMsgType
|
|
10
|
-
| FormatMsgType['rich']
|
|
11
|
-
| FormatMsgType['number']
|
|
12
|
-
| FormatMsgType['date']
|
|
13
|
-
| FormatMsgType['time']
|
|
14
|
-
| FormatMsgType['select']
|
|
15
|
-
| FormatMsgType['plural']
|
|
16
|
-
| FormatMsgType['selectordinal']
|
|
17
|
-
| FormatMsgType['custom'];
|
|
18
|
-
|
|
19
|
-
type WithContextFormatMessage = ReturnType<typeof withContextFormatMessage>;
|
|
20
|
-
|
|
21
|
-
interface TranslateType extends WithContextFormatMessage {
|
|
22
|
-
rich: (
|
|
23
|
-
...args: Parameters<FormatMsgType['rich']>
|
|
24
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
25
|
-
number: (
|
|
26
|
-
...args: Parameters<FormatMsgType['number']>
|
|
27
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
28
|
-
date: (
|
|
29
|
-
...args: Parameters<FormatMsgType['date']>
|
|
30
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
31
|
-
time: (
|
|
32
|
-
...args: Parameters<FormatMsgType['time']>
|
|
33
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
34
|
-
select: (
|
|
35
|
-
...args: Parameters<FormatMsgType['select']>
|
|
36
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
37
|
-
plural: (
|
|
38
|
-
...args: Parameters<FormatMsgType['plural']>
|
|
39
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
40
|
-
selectordinal: (
|
|
41
|
-
...args: Parameters<FormatMsgType['selectordinal']>
|
|
42
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
43
|
-
custom: (
|
|
44
|
-
...args: Parameters<FormatMsgType['custom']>
|
|
45
|
-
) => ReturnType<WithContextFormatMessage>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const withContextFormatMessage =
|
|
49
|
-
(callback: (t: FormatMsgType) => ReturnType<FormatMsgTypes>) =>
|
|
50
|
-
(...args: Parameters<FormatMsgTypes>) =>
|
|
51
|
-
React.createElement(IntlContext.Consumer, null, (t: FormatMsgType) =>
|
|
52
|
-
callback(t)(...args)
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const Translate = withContextFormatMessage((t) => t) as TranslateType;
|
|
56
|
-
Translate.rich = withContextFormatMessage((t) => t.rich);
|
|
57
|
-
Translate.number = withContextFormatMessage((t) => t.number);
|
|
58
|
-
Translate.date = withContextFormatMessage((t) => t.date);
|
|
59
|
-
Translate.time = withContextFormatMessage((t) => t.time);
|
|
60
|
-
Translate.select = withContextFormatMessage((t) => t.select);
|
|
61
|
-
Translate.plural = withContextFormatMessage((t) => t.plural);
|
|
62
|
-
Translate.selectordinal = withContextFormatMessage((t) => t.selectordinal);
|
|
63
|
-
Translate.custom = withContextFormatMessage((t) => t.custom);
|
|
64
|
-
|
|
65
|
-
export const Intl = IntlContext.Consumer;
|
|
66
|
-
export default Translate;
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import IntlContext from '@jetshop/intl/context';
|
|
3
|
+
import formatMessage from 'format-message/types';
|
|
4
|
+
|
|
5
|
+
export const useIntl = () => useContext(IntlContext);
|
|
6
|
+
|
|
7
|
+
type FormatMsgType = typeof formatMessage;
|
|
8
|
+
type FormatMsgTypes =
|
|
9
|
+
| FormatMsgType
|
|
10
|
+
| FormatMsgType['rich']
|
|
11
|
+
| FormatMsgType['number']
|
|
12
|
+
| FormatMsgType['date']
|
|
13
|
+
| FormatMsgType['time']
|
|
14
|
+
| FormatMsgType['select']
|
|
15
|
+
| FormatMsgType['plural']
|
|
16
|
+
| FormatMsgType['selectordinal']
|
|
17
|
+
| FormatMsgType['custom'];
|
|
18
|
+
|
|
19
|
+
type WithContextFormatMessage = ReturnType<typeof withContextFormatMessage>;
|
|
20
|
+
|
|
21
|
+
interface TranslateType extends WithContextFormatMessage {
|
|
22
|
+
rich: (
|
|
23
|
+
...args: Parameters<FormatMsgType['rich']>
|
|
24
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
25
|
+
number: (
|
|
26
|
+
...args: Parameters<FormatMsgType['number']>
|
|
27
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
28
|
+
date: (
|
|
29
|
+
...args: Parameters<FormatMsgType['date']>
|
|
30
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
31
|
+
time: (
|
|
32
|
+
...args: Parameters<FormatMsgType['time']>
|
|
33
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
34
|
+
select: (
|
|
35
|
+
...args: Parameters<FormatMsgType['select']>
|
|
36
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
37
|
+
plural: (
|
|
38
|
+
...args: Parameters<FormatMsgType['plural']>
|
|
39
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
40
|
+
selectordinal: (
|
|
41
|
+
...args: Parameters<FormatMsgType['selectordinal']>
|
|
42
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
43
|
+
custom: (
|
|
44
|
+
...args: Parameters<FormatMsgType['custom']>
|
|
45
|
+
) => ReturnType<WithContextFormatMessage>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const withContextFormatMessage =
|
|
49
|
+
(callback: (t: FormatMsgType) => ReturnType<FormatMsgTypes>) =>
|
|
50
|
+
(...args: Parameters<FormatMsgTypes>) =>
|
|
51
|
+
React.createElement(IntlContext.Consumer, null, (t: FormatMsgType) =>
|
|
52
|
+
callback(t)(...args)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const Translate = withContextFormatMessage((t) => t) as TranslateType;
|
|
56
|
+
Translate.rich = withContextFormatMessage((t) => t.rich);
|
|
57
|
+
Translate.number = withContextFormatMessage((t) => t.number);
|
|
58
|
+
Translate.date = withContextFormatMessage((t) => t.date);
|
|
59
|
+
Translate.time = withContextFormatMessage((t) => t.time);
|
|
60
|
+
Translate.select = withContextFormatMessage((t) => t.select);
|
|
61
|
+
Translate.plural = withContextFormatMessage((t) => t.plural);
|
|
62
|
+
Translate.selectordinal = withContextFormatMessage((t) => t.selectordinal);
|
|
63
|
+
Translate.custom = withContextFormatMessage((t) => t.custom);
|
|
64
|
+
|
|
65
|
+
export const Intl = IntlContext.Consumer;
|
|
66
|
+
export default Translate;
|
package/package.json
CHANGED
package/tooling.mdx
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Tooling
|
|
3
|
-
menu: '@jetshop/intl'
|
|
4
|
-
route: /intl/tooling
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Tooling
|
|
8
|
-
|
|
9
|
-
The `@jetshop/intl` package also exposes some tooling to make translating your shop easier.
|
|
10
|
-
|
|
11
|
-
## Extracting translations
|
|
12
|
-
|
|
13
|
-
Before starting to translate the shop, you need to extract the translation strings from the source code. `@jetshop/intl` provides the command `extract` that searches through your codebase and extracts all translation strings and puts them in `translations/default.json` in your shop.
|
|
14
|
-
By default this script is added to `package.json` in your shop, so you can run this in a terminal:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
yarn intl extract
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Once the translation strings are extracted they get a unique ID, which is used to know where the translation string should be used. Based on the `default.json` file you can then create a file for every locale you want to support, e.g. `sv.json` (Swedish) and `en.json` (English).
|
|
21
|
-
Usually the translation strings in the source code are written in English, so `en.json` should just be a copy of `default.json`.
|
|
22
|
-
|
|
23
|
-
If you add a new locale, make sure to also update the `translations/index.js` file which is an entry point for all translations.
|
|
24
|
-
|
|
25
|
-
When the app is rendered (on server and client) Flight will use the translation provided in your localized json file, and fall back to the default translation if it's not found.
|
|
26
|
-
|
|
27
|
-
## Linting translations
|
|
28
|
-
|
|
29
|
-
In order to make sure your translations are complete, there's also a linting command. This checks all translation files in your `translations/` folder to make sure there's no missing translations anywhere. Run this command in your terminal:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
yarn intl lint
|
|
33
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: Tooling
|
|
3
|
+
menu: '@jetshop/intl'
|
|
4
|
+
route: /intl/tooling
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tooling
|
|
8
|
+
|
|
9
|
+
The `@jetshop/intl` package also exposes some tooling to make translating your shop easier.
|
|
10
|
+
|
|
11
|
+
## Extracting translations
|
|
12
|
+
|
|
13
|
+
Before starting to translate the shop, you need to extract the translation strings from the source code. `@jetshop/intl` provides the command `extract` that searches through your codebase and extracts all translation strings and puts them in `translations/default.json` in your shop.
|
|
14
|
+
By default this script is added to `package.json` in your shop, so you can run this in a terminal:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn intl extract
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Once the translation strings are extracted they get a unique ID, which is used to know where the translation string should be used. Based on the `default.json` file you can then create a file for every locale you want to support, e.g. `sv.json` (Swedish) and `en.json` (English).
|
|
21
|
+
Usually the translation strings in the source code are written in English, so `en.json` should just be a copy of `default.json`.
|
|
22
|
+
|
|
23
|
+
If you add a new locale, make sure to also update the `translations/index.js` file which is an entry point for all translations.
|
|
24
|
+
|
|
25
|
+
When the app is rendered (on server and client) Flight will use the translation provided in your localized json file, and fall back to the default translation if it's not found.
|
|
26
|
+
|
|
27
|
+
## Linting translations
|
|
28
|
+
|
|
29
|
+
In order to make sure your translations are complete, there's also a linting command. This checks all translation files in your `translations/` folder to make sure there's no missing translations anywhere. Run this command in your terminal:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn intl lint
|
|
33
|
+
```
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": ".",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"allowJs": false
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"allowJs": false
|
|
7
|
+
}
|
|
8
|
+
}
|