Skip to content

Commit e67d185

Browse files
committed
feat(react): Add a getFormattedMessage method to Localization
1 parent 82b7dac commit e67d185

File tree

3 files changed

+100
-39
lines changed

3 files changed

+100
-39
lines changed

fluent-react/src/localization.ts

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ export class ReactLocalization {
6464
): string {
6565
const bundle = this.getBundle(id);
6666
if (bundle) {
67-
const msg = bundle.getMessage(id);
68-
if (msg && msg.value) {
67+
const msg = bundle.getMessage(id)!;
68+
if (msg.value) {
6969
let errors: Array<Error> = [];
7070
let value = bundle.formatPattern(msg.value, vars, errors);
7171
for (let error of errors) {
@@ -74,26 +74,51 @@ export class ReactLocalization {
7474
return value;
7575
}
7676
} else {
77-
if (this.areBundlesEmpty()) {
78-
this.reportError(
79-
new Error(
80-
"Attempting to get a string when no localization bundles are " +
81-
"present."
82-
)
83-
);
84-
} else {
85-
this.reportError(
86-
new Error(
87-
`The id "${id}" did not match any messages in the localization ` +
88-
"bundles."
89-
)
90-
);
91-
}
77+
const msg = this.areBundlesEmpty()
78+
? "Attempting to get a string when no localization bundles are present."
79+
: `The id "${id}" did not match any messages in the localization bundles.`;
80+
this.reportError(new Error(msg));
9281
}
9382

9483
return fallback || id;
9584
}
9685

86+
getFormattedMessage(
87+
id: string,
88+
vars?: Record<string, FluentVariable> | null
89+
): {
90+
value: string | null;
91+
attributes?: Record<string, string>;
92+
} {
93+
const bundle = this.getBundle(id);
94+
if (bundle === null) {
95+
const msg = this.areBundlesEmpty()
96+
? "Attempting to get a localized message when no localization bundles are present."
97+
: `The id "${id}" did not match any messages in the localization bundles.`;
98+
this.reportError(new Error(msg));
99+
return { value: null };
100+
}
101+
102+
let value: string | null = null;
103+
let attributes: Record<string, string> | null = null;
104+
const msg = bundle.getMessage(id)!;
105+
let errors: Array<Error> = [];
106+
if (msg.value) {
107+
value = bundle.formatPattern(msg.value, vars, errors);
108+
}
109+
if (msg.attributes) {
110+
attributes = Object.create(null) as Record<string, string>;
111+
for (const [name, pattern] of Object.entries(msg.attributes)) {
112+
attributes[name] = bundle.formatPattern(pattern, vars, errors);
113+
}
114+
}
115+
for (let error of errors) {
116+
this.reportError(error);
117+
}
118+
119+
return attributes ? { value, attributes } : { value };
120+
}
121+
97122
getElement(
98123
sourceElement: ReactElement,
99124
id: string,
@@ -105,26 +130,13 @@ export class ReactLocalization {
105130
): ReactElement {
106131
const bundle = this.getBundle(id);
107132
if (bundle === null) {
108-
if (!id) {
109-
this.reportError(
110-
new Error("No string id was provided when localizing a component.")
111-
);
112-
} else if (this.areBundlesEmpty()) {
113-
this.reportError(
114-
new Error(
115-
"Attempting to get a localized element when no localization bundles are " +
116-
"present."
117-
)
118-
);
119-
} else {
120-
this.reportError(
121-
new Error(
122-
`The id "${id}" did not match any messages in the localization ` +
123-
"bundles."
124-
)
125-
);
126-
}
127-
133+
// eslint-disable-next-line no-nested-ternary
134+
const msg = !id
135+
? "No string id was provided when localizing a component."
136+
: this.areBundlesEmpty()
137+
? "Attempting to get a localized element when no localization bundles are present."
138+
: `The id "${id}" did not match any messages in the localization bundles.`;
139+
this.reportError(new Error(msg));
128140
return createElement(Fragment, null, sourceElement);
129141
}
130142

fluent-react/src/with_localization.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@ import { FluentVariable } from "@fluent/bundle";
55
export interface WithLocalizationProps {
66
getString(
77
id: string,
8-
args?: Record<string, FluentVariable> | null,
8+
vars?: Record<string, FluentVariable> | null,
99
fallback?: string
1010
): string;
11+
getFormattedMessage(
12+
id: string,
13+
vars?: Record<string, FluentVariable> | null
14+
): {
15+
value: string | null;
16+
attributes?: Record<string, string>;
17+
};
1118
}
1219

1320
export function withLocalization<P extends WithLocalizationProps>(
@@ -27,7 +34,12 @@ export function withLocalization<P extends WithLocalizationProps>(
2734
}
2835
// Re-bind getString to trigger a re-render of Inner.
2936
const getString = l10n.getString.bind(l10n);
30-
return createElement(Inner, { getString, ...props } as P);
37+
const getFormattedMessage = l10n.getFormattedMessage.bind(l10n);
38+
return createElement(Inner, {
39+
getString,
40+
getFormattedMessage,
41+
...props,
42+
} as P);
3143
}
3244

3345
WithLocalization.displayName = `WithLocalization(${displayName(Inner)})`;

fluent-react/test/with_localization.test.jsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,41 @@ bar = BAR {$arg}
188188

189189
expect(renderer.toJSON()).toMatchInlineSnapshot(`"BAR"`);
190190
});
191+
192+
test("getFormattedMessage with access to the l10n context", () => {
193+
vi.spyOn(console, "warn").mockImplementation(() => {});
194+
const bundle = new FluentBundle("en", { useIsolating: false });
195+
const EnhancedComponent = withLocalization(DummyComponent);
196+
197+
bundle.addResource(
198+
new FluentResource(`
199+
foo = FOO
200+
.bar = BAR {$arg}
201+
`)
202+
);
203+
204+
const renderer = TestRenderer.create(
205+
<LocalizationProvider l10n={new ReactLocalization([bundle])}>
206+
<EnhancedComponent />
207+
</LocalizationProvider>
208+
);
209+
210+
const { getFormattedMessage } =
211+
renderer.root.findByType(DummyComponent).props;
212+
213+
// Returns the translation.
214+
expect(getFormattedMessage("foo", { arg: "ARG" })).toEqual({
215+
value: "FOO",
216+
attributes: { bar: "BAR ARG" },
217+
});
218+
219+
// It reports an error on formatting errors, but doesn't throw.
220+
expect(getFormattedMessage("foo", {})).toEqual({
221+
value: "FOO",
222+
attributes: { bar: "BAR {$arg}" },
223+
});
224+
expect(console.warn.mock.calls).toEqual([
225+
["[@fluent/react] ReferenceError: Unknown variable: $arg"],
226+
]);
227+
});
191228
});

0 commit comments

Comments
 (0)