@worldware/msg 0.6.3 → 0.7.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 +20 -4
- package/dist/{chunk-7LI2M4JY.mjs → chunk-2QUOYESW.mjs} +39 -1
- package/dist/classes/MsgProject/MsgProject.d.cts +1 -0
- package/dist/classes/MsgProject/MsgProject.d.ts +1 -0
- package/dist/classes/MsgResource/MsgResource.cjs +39 -1
- package/dist/classes/MsgResource/MsgResource.mjs +1 -1
- package/dist/classes/index.cjs +39 -1
- package/dist/classes/index.mjs +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A TypeScript library for managing internationalization (i18n) messages with supp
|
|
|
8
8
|
|
|
9
9
|
- **Message Management**: Organize messages into resources with keys and values
|
|
10
10
|
- **Translation Loading**: Load translations from external sources via customizable loaders
|
|
11
|
+
- **Pseudo Localization**: Request a pseudolocalized resource for UI testing via `getTranslation(pseudoLocale)`
|
|
11
12
|
- **Message Formatting**: Format messages with parameters using MessageFormat 2 (MF2) syntax
|
|
12
13
|
- **Attributes & Notes**: Attach metadata (language, direction, do-not-translate flags) and notes to messages
|
|
13
14
|
- **Project Configuration**: Configure projects with locale settings and translation loaders
|
|
@@ -25,6 +26,7 @@ npm install @worldware/msg
|
|
|
25
26
|
A project configuration that defines:
|
|
26
27
|
- Project name and version
|
|
27
28
|
- Source and target locales (with language fallback chains)
|
|
29
|
+
- Pseudo locale (for pseudolocalized output via `getTranslation`)
|
|
28
30
|
- A translation loader function
|
|
29
31
|
|
|
30
32
|
### MsgResource
|
|
@@ -89,7 +91,7 @@ const resource = MsgResource.create({
|
|
|
89
91
|
messages: [
|
|
90
92
|
{
|
|
91
93
|
key: 'greeting',
|
|
92
|
-
value: 'Hello, {name}!'
|
|
94
|
+
value: 'Hello, {$name}!'
|
|
93
95
|
},
|
|
94
96
|
{
|
|
95
97
|
key: 'welcome',
|
|
@@ -99,7 +101,7 @@ const resource = MsgResource.create({
|
|
|
99
101
|
}, project);
|
|
100
102
|
|
|
101
103
|
// Or add messages programmatically
|
|
102
|
-
resource.add('goodbye', 'Goodbye, {name}!', {
|
|
104
|
+
resource.add('goodbye', 'Goodbye, {$name}!', {
|
|
103
105
|
lang: 'en',
|
|
104
106
|
dir: 'ltr'
|
|
105
107
|
});
|
|
@@ -124,11 +126,25 @@ const spanishResource = await resource.getTranslation('es');
|
|
|
124
126
|
// falling back to the source messages for missing translations
|
|
125
127
|
```
|
|
126
128
|
|
|
129
|
+
### Pseudo Localization
|
|
130
|
+
|
|
131
|
+
When `getTranslation` is called with the project's `pseudoLocale` (e.g. `en-XA`), it returns a new resource with pseudolocalized message values—useful for testing UI layout and finding hardcoded strings without loading translation files:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Request pseudolocalized messages (project locales.pseudoLocale is 'en-XA')
|
|
135
|
+
const pseudoResource = await resource.getTranslation('en-XA');
|
|
136
|
+
|
|
137
|
+
// Message values are transformed: "Hello, {$name}!" → "Ħḗḗŀŀǿǿ, {$name}!"
|
|
138
|
+
// Variables and MF2 syntax are preserved; only literal text is pseudolocalized
|
|
139
|
+
const greeting = pseudoResource.get('greeting')?.format({ name: 'Alice' });
|
|
140
|
+
// Result: "Ħḗḗŀŀǿǿ, Alice!"
|
|
141
|
+
```
|
|
142
|
+
|
|
127
143
|
### Working with Attributes and Notes
|
|
128
144
|
|
|
129
145
|
```typescript
|
|
130
146
|
// Add notes to messages
|
|
131
|
-
resource.add('complex-message', '
|
|
147
|
+
resource.add('complex-message', 'You have {$count} items', {
|
|
132
148
|
lang: 'en',
|
|
133
149
|
dir: 'ltr',
|
|
134
150
|
dnt: false // do-not-translate flag
|
|
@@ -188,7 +204,7 @@ const data = resource.getData();
|
|
|
188
204
|
**Methods:**
|
|
189
205
|
- `add(key: string, value: string, attributes?: MsgAttributes, notes?: MsgNote[]): MsgResource` - Add a message
|
|
190
206
|
- `translate(data: MsgResourceData): MsgResource` - Create a translated version
|
|
191
|
-
- `getTranslation(lang: string): Promise<MsgResource>` - Load and apply translations
|
|
207
|
+
- `getTranslation(lang: string): Promise<MsgResource>` - Load and apply translations. When `lang` matches the project's `pseudoLocale`, returns a resource with pseudolocalized message values instead of loading from the loader.
|
|
192
208
|
- `getProject(): MsgProject` - Returns the project instance associated with the resource
|
|
193
209
|
- `getData(stripNotes?: boolean): MsgResourceData` - Get resource data. Message objects in the output omit `attributes` when they match the resource's attributes (to avoid redundancy)
|
|
194
210
|
- `toJSON(stripNotes?: boolean): string` - Serialize to JSON
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
} from "./chunk-QWBDIKQK.mjs";
|
|
7
7
|
|
|
8
8
|
// src/classes/MsgResource/MsgResource.ts
|
|
9
|
+
import { parseMessage, stringifyMessage, visit } from "messageformat";
|
|
10
|
+
import { localize } from "pseudo-localization";
|
|
9
11
|
var MsgResource = class _MsgResource extends Map {
|
|
10
12
|
_attributes = {};
|
|
11
13
|
_notes = [];
|
|
@@ -38,6 +40,20 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
38
40
|
const msg = message.attributes;
|
|
39
41
|
return res.lang === msg.lang && res.dir === msg.dir && res.dnt === msg.dnt;
|
|
40
42
|
}
|
|
43
|
+
pseudoLocalizeMF2(source, options) {
|
|
44
|
+
const msg = parseMessage(source);
|
|
45
|
+
visit(msg, {
|
|
46
|
+
pattern: (pattern) => {
|
|
47
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
48
|
+
const part = pattern[i];
|
|
49
|
+
if (typeof part === "string") {
|
|
50
|
+
pattern[i] = localize(part, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return stringifyMessage(msg);
|
|
56
|
+
}
|
|
41
57
|
get attributes() {
|
|
42
58
|
return this._attributes;
|
|
43
59
|
}
|
|
@@ -104,6 +120,23 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
104
120
|
}
|
|
105
121
|
async getTranslation(lang) {
|
|
106
122
|
const project = this._project;
|
|
123
|
+
const pseudoLocale = project.locales.pseudoLocale;
|
|
124
|
+
if (lang === pseudoLocale) {
|
|
125
|
+
const pseudolocalizedData = {
|
|
126
|
+
title: this.title,
|
|
127
|
+
attributes: { ...this.attributes, lang: pseudoLocale },
|
|
128
|
+
notes: this.notes.length > 0 ? this.notes : void 0,
|
|
129
|
+
messages: []
|
|
130
|
+
};
|
|
131
|
+
this.forEach((msg) => {
|
|
132
|
+
pseudolocalizedData.messages.push({
|
|
133
|
+
key: msg.key,
|
|
134
|
+
value: this.pseudoLocalizeMF2(msg.value),
|
|
135
|
+
attributes: { ...this.attributes, lang: pseudoLocale }
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
return this.translate(pseudolocalizedData);
|
|
139
|
+
}
|
|
107
140
|
const languageChain = project.getTargetLocale(lang);
|
|
108
141
|
if (!languageChain) {
|
|
109
142
|
throw new Error("Unsupported locale for resource.");
|
|
@@ -124,7 +157,12 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
124
157
|
const messages = [];
|
|
125
158
|
this.forEach((msg) => {
|
|
126
159
|
if (this.hasMatchingAttributes(msg)) {
|
|
127
|
-
|
|
160
|
+
const data = {
|
|
161
|
+
key: msg.key,
|
|
162
|
+
value: msg.value,
|
|
163
|
+
notes: !stripNotes && msg.notes.length > 0 ? msg.notes : void 0
|
|
164
|
+
};
|
|
165
|
+
messages.push(data);
|
|
128
166
|
} else {
|
|
129
167
|
messages.push(msg.getData(stripNotes));
|
|
130
168
|
}
|
|
@@ -46,6 +46,7 @@ declare class MsgResource extends Map<string, MsgMessage> implements MsgInterfac
|
|
|
46
46
|
static create(data: MsgResourceData, project: MsgProject): MsgResource;
|
|
47
47
|
private constructor();
|
|
48
48
|
private hasMatchingAttributes;
|
|
49
|
+
private pseudoLocalizeMF2;
|
|
49
50
|
get attributes(): MsgAttributes;
|
|
50
51
|
set attributes(attributes: MsgAttributes);
|
|
51
52
|
get notes(): MsgNote[];
|
|
@@ -46,6 +46,7 @@ declare class MsgResource extends Map<string, MsgMessage> implements MsgInterfac
|
|
|
46
46
|
static create(data: MsgResourceData, project: MsgProject): MsgResource;
|
|
47
47
|
private constructor();
|
|
48
48
|
private hasMatchingAttributes;
|
|
49
|
+
private pseudoLocalizeMF2;
|
|
49
50
|
get attributes(): MsgAttributes;
|
|
50
51
|
set attributes(attributes: MsgAttributes);
|
|
51
52
|
get notes(): MsgNote[];
|
|
@@ -23,6 +23,8 @@ __export(MsgResource_exports, {
|
|
|
23
23
|
MsgResource: () => MsgResource
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(MsgResource_exports);
|
|
26
|
+
var import_messageformat2 = require("messageformat");
|
|
27
|
+
var import_pseudo_localization = require("pseudo-localization");
|
|
26
28
|
|
|
27
29
|
// src/classes/MsgMessage/MsgMessage.ts
|
|
28
30
|
var import_messageformat = require("messageformat");
|
|
@@ -130,6 +132,20 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
130
132
|
const msg = message.attributes;
|
|
131
133
|
return res.lang === msg.lang && res.dir === msg.dir && res.dnt === msg.dnt;
|
|
132
134
|
}
|
|
135
|
+
pseudoLocalizeMF2(source, options) {
|
|
136
|
+
const msg = (0, import_messageformat2.parseMessage)(source);
|
|
137
|
+
(0, import_messageformat2.visit)(msg, {
|
|
138
|
+
pattern: (pattern) => {
|
|
139
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
140
|
+
const part = pattern[i];
|
|
141
|
+
if (typeof part === "string") {
|
|
142
|
+
pattern[i] = (0, import_pseudo_localization.localize)(part, options);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return (0, import_messageformat2.stringifyMessage)(msg);
|
|
148
|
+
}
|
|
133
149
|
get attributes() {
|
|
134
150
|
return this._attributes;
|
|
135
151
|
}
|
|
@@ -196,6 +212,23 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
196
212
|
}
|
|
197
213
|
async getTranslation(lang) {
|
|
198
214
|
const project = this._project;
|
|
215
|
+
const pseudoLocale = project.locales.pseudoLocale;
|
|
216
|
+
if (lang === pseudoLocale) {
|
|
217
|
+
const pseudolocalizedData = {
|
|
218
|
+
title: this.title,
|
|
219
|
+
attributes: { ...this.attributes, lang: pseudoLocale },
|
|
220
|
+
notes: this.notes.length > 0 ? this.notes : void 0,
|
|
221
|
+
messages: []
|
|
222
|
+
};
|
|
223
|
+
this.forEach((msg) => {
|
|
224
|
+
pseudolocalizedData.messages.push({
|
|
225
|
+
key: msg.key,
|
|
226
|
+
value: this.pseudoLocalizeMF2(msg.value),
|
|
227
|
+
attributes: { ...this.attributes, lang: pseudoLocale }
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
return this.translate(pseudolocalizedData);
|
|
231
|
+
}
|
|
199
232
|
const languageChain = project.getTargetLocale(lang);
|
|
200
233
|
if (!languageChain) {
|
|
201
234
|
throw new Error("Unsupported locale for resource.");
|
|
@@ -216,7 +249,12 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
216
249
|
const messages = [];
|
|
217
250
|
this.forEach((msg) => {
|
|
218
251
|
if (this.hasMatchingAttributes(msg)) {
|
|
219
|
-
|
|
252
|
+
const data = {
|
|
253
|
+
key: msg.key,
|
|
254
|
+
value: msg.value,
|
|
255
|
+
notes: !stripNotes && msg.notes.length > 0 ? msg.notes : void 0
|
|
256
|
+
};
|
|
257
|
+
messages.push(data);
|
|
220
258
|
} else {
|
|
221
259
|
messages.push(msg.getData(stripNotes));
|
|
222
260
|
}
|
package/dist/classes/index.cjs
CHANGED
|
@@ -100,6 +100,8 @@ var MsgMessage = class _MsgMessage {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
// src/classes/MsgResource/MsgResource.ts
|
|
103
|
+
var import_messageformat2 = require("messageformat");
|
|
104
|
+
var import_pseudo_localization = require("pseudo-localization");
|
|
103
105
|
var MsgResource = class _MsgResource extends Map {
|
|
104
106
|
_attributes = {};
|
|
105
107
|
_notes = [];
|
|
@@ -132,6 +134,20 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
132
134
|
const msg = message.attributes;
|
|
133
135
|
return res.lang === msg.lang && res.dir === msg.dir && res.dnt === msg.dnt;
|
|
134
136
|
}
|
|
137
|
+
pseudoLocalizeMF2(source, options) {
|
|
138
|
+
const msg = (0, import_messageformat2.parseMessage)(source);
|
|
139
|
+
(0, import_messageformat2.visit)(msg, {
|
|
140
|
+
pattern: (pattern) => {
|
|
141
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
142
|
+
const part = pattern[i];
|
|
143
|
+
if (typeof part === "string") {
|
|
144
|
+
pattern[i] = (0, import_pseudo_localization.localize)(part, options);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return (0, import_messageformat2.stringifyMessage)(msg);
|
|
150
|
+
}
|
|
135
151
|
get attributes() {
|
|
136
152
|
return this._attributes;
|
|
137
153
|
}
|
|
@@ -198,6 +214,23 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
198
214
|
}
|
|
199
215
|
async getTranslation(lang) {
|
|
200
216
|
const project = this._project;
|
|
217
|
+
const pseudoLocale = project.locales.pseudoLocale;
|
|
218
|
+
if (lang === pseudoLocale) {
|
|
219
|
+
const pseudolocalizedData = {
|
|
220
|
+
title: this.title,
|
|
221
|
+
attributes: { ...this.attributes, lang: pseudoLocale },
|
|
222
|
+
notes: this.notes.length > 0 ? this.notes : void 0,
|
|
223
|
+
messages: []
|
|
224
|
+
};
|
|
225
|
+
this.forEach((msg) => {
|
|
226
|
+
pseudolocalizedData.messages.push({
|
|
227
|
+
key: msg.key,
|
|
228
|
+
value: this.pseudoLocalizeMF2(msg.value),
|
|
229
|
+
attributes: { ...this.attributes, lang: pseudoLocale }
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
return this.translate(pseudolocalizedData);
|
|
233
|
+
}
|
|
201
234
|
const languageChain = project.getTargetLocale(lang);
|
|
202
235
|
if (!languageChain) {
|
|
203
236
|
throw new Error("Unsupported locale for resource.");
|
|
@@ -218,7 +251,12 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
218
251
|
const messages = [];
|
|
219
252
|
this.forEach((msg) => {
|
|
220
253
|
if (this.hasMatchingAttributes(msg)) {
|
|
221
|
-
|
|
254
|
+
const data = {
|
|
255
|
+
key: msg.key,
|
|
256
|
+
value: msg.value,
|
|
257
|
+
notes: !stripNotes && msg.notes.length > 0 ? msg.notes : void 0
|
|
258
|
+
};
|
|
259
|
+
messages.push(data);
|
|
222
260
|
} else {
|
|
223
261
|
messages.push(msg.getData(stripNotes));
|
|
224
262
|
}
|
package/dist/classes/index.mjs
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -100,6 +100,8 @@ var MsgMessage = class _MsgMessage {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
// src/classes/MsgResource/MsgResource.ts
|
|
103
|
+
var import_messageformat2 = require("messageformat");
|
|
104
|
+
var import_pseudo_localization = require("pseudo-localization");
|
|
103
105
|
var MsgResource = class _MsgResource extends Map {
|
|
104
106
|
_attributes = {};
|
|
105
107
|
_notes = [];
|
|
@@ -132,6 +134,20 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
132
134
|
const msg = message.attributes;
|
|
133
135
|
return res.lang === msg.lang && res.dir === msg.dir && res.dnt === msg.dnt;
|
|
134
136
|
}
|
|
137
|
+
pseudoLocalizeMF2(source, options) {
|
|
138
|
+
const msg = (0, import_messageformat2.parseMessage)(source);
|
|
139
|
+
(0, import_messageformat2.visit)(msg, {
|
|
140
|
+
pattern: (pattern) => {
|
|
141
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
142
|
+
const part = pattern[i];
|
|
143
|
+
if (typeof part === "string") {
|
|
144
|
+
pattern[i] = (0, import_pseudo_localization.localize)(part, options);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return (0, import_messageformat2.stringifyMessage)(msg);
|
|
150
|
+
}
|
|
135
151
|
get attributes() {
|
|
136
152
|
return this._attributes;
|
|
137
153
|
}
|
|
@@ -198,6 +214,23 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
198
214
|
}
|
|
199
215
|
async getTranslation(lang) {
|
|
200
216
|
const project = this._project;
|
|
217
|
+
const pseudoLocale = project.locales.pseudoLocale;
|
|
218
|
+
if (lang === pseudoLocale) {
|
|
219
|
+
const pseudolocalizedData = {
|
|
220
|
+
title: this.title,
|
|
221
|
+
attributes: { ...this.attributes, lang: pseudoLocale },
|
|
222
|
+
notes: this.notes.length > 0 ? this.notes : void 0,
|
|
223
|
+
messages: []
|
|
224
|
+
};
|
|
225
|
+
this.forEach((msg) => {
|
|
226
|
+
pseudolocalizedData.messages.push({
|
|
227
|
+
key: msg.key,
|
|
228
|
+
value: this.pseudoLocalizeMF2(msg.value),
|
|
229
|
+
attributes: { ...this.attributes, lang: pseudoLocale }
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
return this.translate(pseudolocalizedData);
|
|
233
|
+
}
|
|
201
234
|
const languageChain = project.getTargetLocale(lang);
|
|
202
235
|
if (!languageChain) {
|
|
203
236
|
throw new Error("Unsupported locale for resource.");
|
|
@@ -218,7 +251,12 @@ var MsgResource = class _MsgResource extends Map {
|
|
|
218
251
|
const messages = [];
|
|
219
252
|
this.forEach((msg) => {
|
|
220
253
|
if (this.hasMatchingAttributes(msg)) {
|
|
221
|
-
|
|
254
|
+
const data = {
|
|
255
|
+
key: msg.key,
|
|
256
|
+
value: msg.value,
|
|
257
|
+
notes: !stripNotes && msg.notes.length > 0 ? msg.notes : void 0
|
|
258
|
+
};
|
|
259
|
+
messages.push(data);
|
|
222
260
|
} else {
|
|
223
261
|
messages.push(msg.getData(stripNotes));
|
|
224
262
|
}
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worldware/msg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Message localization tooling",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joel Sahleen",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"vitest": "^4.0.15"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"messageformat": "4.0.0-10"
|
|
41
|
+
"messageformat": "4.0.0-10",
|
|
42
|
+
"pseudo-localization": "^2.4.0"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
44
45
|
"build": "tsup",
|