@strato-admin/i18n 0.1.0 → 0.3.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/dist/formatter.d.ts +10 -0
- package/dist/formatter.js +58 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.js +10 -2
- package/dist/icuI18nProvider.d.ts +1 -1
- package/dist/icuI18nProvider.js +9 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +7 -5
- package/src/formatter.ts +65 -0
- package/src/hash.ts +11 -2
- package/src/icuI18nProvider.ts +9 -3
- package/src/index.ts +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pretty-prints an ICU message for better readability in translation files.
|
|
3
|
+
* Uses newlines and indentation for nested structures.
|
|
4
|
+
*/
|
|
5
|
+
export declare function prettyPrintICU(msg: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Canonicalizes an ICU message using the official printer and normalization.
|
|
8
|
+
* This ensures consistent spacing and a single-line format for hashing and compilation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canonicalizeMessage(msg: string): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { parse, isPluralElement, isSelectElement, isTagElement, isLiteralElement, isPoundElement, } from '@formatjs/icu-messageformat-parser';
|
|
2
|
+
import { printAST } from '@formatjs/icu-messageformat-parser/printer.js';
|
|
3
|
+
import { normalizeMessage } from './hash';
|
|
4
|
+
/**
|
|
5
|
+
* Pretty-prints an ICU message for better readability in translation files.
|
|
6
|
+
* Uses newlines and indentation for nested structures.
|
|
7
|
+
*/
|
|
8
|
+
export function prettyPrintICU(msg) {
|
|
9
|
+
try {
|
|
10
|
+
const ast = parse(msg);
|
|
11
|
+
return printElement(ast, 0).trim();
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return msg.trim();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function printElement(elements, indent) {
|
|
18
|
+
const padding = ' '.repeat(indent);
|
|
19
|
+
let result = '';
|
|
20
|
+
elements.forEach((el) => {
|
|
21
|
+
if (isLiteralElement(el)) {
|
|
22
|
+
result += el.value;
|
|
23
|
+
}
|
|
24
|
+
else if (isPluralElement(el) || isSelectElement(el)) {
|
|
25
|
+
const type = isPluralElement(el) ? 'plural' : 'select';
|
|
26
|
+
result += `{${el.value}, ${type},\n`;
|
|
27
|
+
const options = Object.entries(el.options);
|
|
28
|
+
options.forEach(([key, opt], _) => {
|
|
29
|
+
result += `${padding} ${key} {${printElement(opt.value, indent + 2)}}\n`;
|
|
30
|
+
});
|
|
31
|
+
result += `${padding}}`;
|
|
32
|
+
}
|
|
33
|
+
else if (isTagElement(el)) {
|
|
34
|
+
result += `<${el.value}>${printElement(el.children, indent)}</${el.value}>`;
|
|
35
|
+
}
|
|
36
|
+
else if (isPoundElement(el)) {
|
|
37
|
+
result += '#';
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// For arguments like {name}
|
|
41
|
+
result += `{${el.value || ''}}`;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Canonicalizes an ICU message using the official printer and normalization.
|
|
48
|
+
* This ensures consistent spacing and a single-line format for hashing and compilation.
|
|
49
|
+
*/
|
|
50
|
+
export function canonicalizeMessage(msg) {
|
|
51
|
+
try {
|
|
52
|
+
const ast = parse(msg);
|
|
53
|
+
return normalizeMessage(printAST(ast));
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
return normalizeMessage(msg);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/hash.d.ts
CHANGED
|
@@ -3,3 +3,8 @@
|
|
|
3
3
|
* This is lightweight and works identically in Node and the Browser.
|
|
4
4
|
*/
|
|
5
5
|
export declare function generateMessageId(msg: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes a message string by collapsing multiple whitespaces and newlines.
|
|
8
|
+
* This ensures that formatting changes in the source code do not change the message ID.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeMessage(msg: string): string;
|
package/dist/hash.js
CHANGED
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
* This is lightweight and works identically in Node and the Browser.
|
|
4
4
|
*/
|
|
5
5
|
export function generateMessageId(msg) {
|
|
6
|
+
const normalized = normalizeMessage(msg);
|
|
6
7
|
let hash = 0x811c9dc5;
|
|
7
|
-
for (let i = 0; i <
|
|
8
|
-
hash ^=
|
|
8
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
9
|
+
hash ^= normalized.charCodeAt(i);
|
|
9
10
|
// FNV-1a prime multiplication (hash * 16777619)
|
|
10
11
|
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
11
12
|
}
|
|
12
13
|
// Convert to a base36 string for a compact, stable ID
|
|
13
14
|
return (hash >>> 0).toString(36);
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes a message string by collapsing multiple whitespaces and newlines.
|
|
18
|
+
* This ensures that formatting changes in the source code do not change the message ID.
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeMessage(msg) {
|
|
21
|
+
return msg.replace(/\s+/g, ' ').trim();
|
|
22
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { I18nProvider } from '@strato-admin/core';
|
|
1
|
+
import type { I18nProvider } from '@strato-admin/ra-core';
|
|
2
2
|
export declare const icuI18nProvider: (getMessages: (locale: string) => any | Promise<any>, initialLocale?: string, availableLocales?: any[]) => I18nProvider;
|
package/dist/icuI18nProvider.js
CHANGED
|
@@ -33,7 +33,15 @@ export const icuI18nProvider = (getMessages, initialLocale = 'en', availableLoca
|
|
|
33
33
|
// 2. Lookup by hash first, then fall back to literal key (for ra.* keys)
|
|
34
34
|
const message = messages[msgid] || messages[finalKey];
|
|
35
35
|
if (message === undefined) {
|
|
36
|
-
|
|
36
|
+
if (defaultMessage === undefined)
|
|
37
|
+
return finalKey;
|
|
38
|
+
try {
|
|
39
|
+
const formatter = new IntlMessageFormat(defaultMessage, locale);
|
|
40
|
+
return formatter.format(values);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return defaultMessage;
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
if (typeof message !== 'string') {
|
|
39
47
|
return finalKey;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strato-admin/i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Strato Admin I18n - ICU i18nProvider for React Admin / Strato Admin",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
|
-
"
|
|
10
|
+
"development": "./src/index.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
11
12
|
}
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
@@ -30,20 +31,21 @@
|
|
|
30
31
|
"license": "MIT",
|
|
31
32
|
"repository": {
|
|
32
33
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/
|
|
34
|
+
"url": "https://github.com/vadimgu/strato-admin.git",
|
|
34
35
|
"directory": "packages/strato-i18n"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
|
-
"@strato-admin/core": "0.
|
|
38
|
+
"@strato-admin/core": "0.3.0"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
41
|
+
"@formatjs/icu-messageformat-parser": "^3.5.3",
|
|
40
42
|
"intl-messageformat": "^11.1.2"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/node": "^25.5.0",
|
|
44
46
|
"typescript": "^5.9.3",
|
|
45
47
|
"vitest": "^3.0.7",
|
|
46
|
-
"@strato-admin/ra-core": "^0.
|
|
48
|
+
"@strato-admin/ra-core": "^0.3.0"
|
|
47
49
|
},
|
|
48
50
|
"scripts": {
|
|
49
51
|
"build": "tsc -p tsconfig.build.json",
|
package/src/formatter.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parse,
|
|
3
|
+
MessageFormatElement,
|
|
4
|
+
isPluralElement,
|
|
5
|
+
isSelectElement,
|
|
6
|
+
isTagElement,
|
|
7
|
+
isLiteralElement,
|
|
8
|
+
isPoundElement,
|
|
9
|
+
} from '@formatjs/icu-messageformat-parser';
|
|
10
|
+
import { printAST } from '@formatjs/icu-messageformat-parser/printer.js';
|
|
11
|
+
import { normalizeMessage } from './hash';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pretty-prints an ICU message for better readability in translation files.
|
|
15
|
+
* Uses newlines and indentation for nested structures.
|
|
16
|
+
*/
|
|
17
|
+
export function prettyPrintICU(msg: string): string {
|
|
18
|
+
try {
|
|
19
|
+
const ast = parse(msg);
|
|
20
|
+
return printElement(ast, 0).trim();
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return msg.trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function printElement(elements: MessageFormatElement[], indent: number): string {
|
|
27
|
+
const padding = ' '.repeat(indent);
|
|
28
|
+
let result = '';
|
|
29
|
+
|
|
30
|
+
elements.forEach((el) => {
|
|
31
|
+
if (isLiteralElement(el)) {
|
|
32
|
+
result += el.value;
|
|
33
|
+
} else if (isPluralElement(el) || isSelectElement(el)) {
|
|
34
|
+
const type = isPluralElement(el) ? 'plural' : 'select';
|
|
35
|
+
result += `{${el.value}, ${type},\n`;
|
|
36
|
+
const options = Object.entries(el.options);
|
|
37
|
+
options.forEach(([key, opt], _) => {
|
|
38
|
+
result += `${padding} ${key} {${printElement(opt.value, indent + 2)}}\n`;
|
|
39
|
+
});
|
|
40
|
+
result += `${padding}}`;
|
|
41
|
+
} else if (isTagElement(el)) {
|
|
42
|
+
result += `<${el.value}>${printElement(el.children, indent)}</${el.value}>`;
|
|
43
|
+
} else if (isPoundElement(el)) {
|
|
44
|
+
result += '#';
|
|
45
|
+
} else {
|
|
46
|
+
// For arguments like {name}
|
|
47
|
+
result += `{${(el as any).value || ''}}`;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Canonicalizes an ICU message using the official printer and normalization.
|
|
56
|
+
* This ensures consistent spacing and a single-line format for hashing and compilation.
|
|
57
|
+
*/
|
|
58
|
+
export function canonicalizeMessage(msg: string): string {
|
|
59
|
+
try {
|
|
60
|
+
const ast = parse(msg);
|
|
61
|
+
return normalizeMessage(printAST(ast));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
return normalizeMessage(msg);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/hash.ts
CHANGED
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
* This is lightweight and works identically in Node and the Browser.
|
|
4
4
|
*/
|
|
5
5
|
export function generateMessageId(msg: string): string {
|
|
6
|
+
const normalized = normalizeMessage(msg);
|
|
6
7
|
let hash = 0x811c9dc5;
|
|
7
|
-
for (let i = 0; i <
|
|
8
|
-
hash ^=
|
|
8
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
9
|
+
hash ^= normalized.charCodeAt(i);
|
|
9
10
|
// FNV-1a prime multiplication (hash * 16777619)
|
|
10
11
|
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
11
12
|
}
|
|
12
13
|
// Convert to a base36 string for a compact, stable ID
|
|
13
14
|
return (hash >>> 0).toString(36);
|
|
14
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes a message string by collapsing multiple whitespaces and newlines.
|
|
19
|
+
* This ensures that formatting changes in the source code do not change the message ID.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeMessage(msg: string): string {
|
|
22
|
+
return msg.replace(/\s+/g, ' ').trim();
|
|
23
|
+
}
|
package/src/icuI18nProvider.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IntlMessageFormat } from 'intl-messageformat';
|
|
2
|
-
import type { I18nProvider
|
|
2
|
+
import type { I18nProvider } from '@strato-admin/ra-core';
|
|
3
3
|
import { generateMessageId } from './hash';
|
|
4
4
|
|
|
5
5
|
export const icuI18nProvider = (
|
|
@@ -41,12 +41,18 @@ export const icuI18nProvider = (
|
|
|
41
41
|
|
|
42
42
|
// 1. Generate the hash ID for the English key
|
|
43
43
|
const msgid = generateMessageId(finalKey);
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
// 2. Lookup by hash first, then fall back to literal key (for ra.* keys)
|
|
46
46
|
const message = (messages as any)[msgid] || (messages as any)[finalKey];
|
|
47
47
|
|
|
48
48
|
if (message === undefined) {
|
|
49
|
-
|
|
49
|
+
if (defaultMessage === undefined) return finalKey;
|
|
50
|
+
try {
|
|
51
|
+
const formatter = new IntlMessageFormat(defaultMessage, locale);
|
|
52
|
+
return formatter.format(values) as string;
|
|
53
|
+
} catch {
|
|
54
|
+
return defaultMessage;
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
if (typeof message !== 'string') {
|
package/src/index.ts
CHANGED