Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 49 additions & 37 deletions fluent-react/src/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export class ReactLocalization {
): string {
const bundle = this.getBundle(id);
if (bundle) {
const msg = bundle.getMessage(id);
if (msg && msg.value) {
const msg = bundle.getMessage(id)!;
if (msg.value) {
let errors: Array<Error> = [];
let value = bundle.formatPattern(msg.value, vars, errors);
for (let error of errors) {
Expand All @@ -74,26 +74,51 @@ export class ReactLocalization {
return value;
}
} else {
if (this.areBundlesEmpty()) {
this.reportError(
new Error(
"Attempting to get a string when no localization bundles are " +
"present."
)
);
} else {
this.reportError(
new Error(
`The id "${id}" did not match any messages in the localization ` +
"bundles."
)
);
}
const msg = this.areBundlesEmpty()
? "Attempting to get a string when no localization bundles are present."
: `The id "${id}" did not match any messages in the localization bundles.`;
this.reportError(new Error(msg));
}

return fallback || id;
}

getFormattedMessage(
id: string,
vars?: Record<string, FluentVariable> | null
): {
value: string | null;
attributes?: Record<string, string>;
} {
const bundle = this.getBundle(id);
if (bundle === null) {
const msg = this.areBundlesEmpty()
? "Attempting to get a localized message when no localization bundles are present."
: `The id "${id}" did not match any messages in the localization bundles.`;
this.reportError(new Error(msg));
return { value: null };
}

let value: string | null = null;
let attributes: Record<string, string> | null = null;
const msg = bundle.getMessage(id)!;
let errors: Array<Error> = [];
if (msg.value) {
value = bundle.formatPattern(msg.value, vars, errors);
}
if (msg.attributes) {
attributes = Object.create(null) as Record<string, string>;
for (const [name, pattern] of Object.entries(msg.attributes)) {
attributes[name] = bundle.formatPattern(pattern, vars, errors);
}
}
for (let error of errors) {
this.reportError(error);
}

return attributes ? { value, attributes } : { value };
}

getElement(
sourceElement: ReactElement,
id: string,
Expand All @@ -105,26 +130,13 @@ export class ReactLocalization {
): ReactElement {
const bundle = this.getBundle(id);
if (bundle === null) {
if (!id) {
this.reportError(
new Error("No string id was provided when localizing a component.")
);
} else if (this.areBundlesEmpty()) {
this.reportError(
new Error(
"Attempting to get a localized element when no localization bundles are " +
"present."
)
);
} else {
this.reportError(
new Error(
`The id "${id}" did not match any messages in the localization ` +
"bundles."
)
);
}

// eslint-disable-next-line no-nested-ternary
const msg = !id
? "No string id was provided when localizing a component."
: this.areBundlesEmpty()
? "Attempting to get a localized element when no localization bundles are present."
: `The id "${id}" did not match any messages in the localization bundles.`;
this.reportError(new Error(msg));
return createElement(Fragment, null, sourceElement);
}

Expand Down
16 changes: 14 additions & 2 deletions fluent-react/src/with_localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ import { FluentVariable } from "@fluent/bundle";
export interface WithLocalizationProps {
getString(
id: string,
args?: Record<string, FluentVariable> | null,
vars?: Record<string, FluentVariable> | null,
fallback?: string
): string;
getFormattedMessage(
id: string,
vars?: Record<string, FluentVariable> | null
): {
value: string | null;
attributes?: Record<string, string>;
};
}

export function withLocalization<P extends WithLocalizationProps>(
Expand All @@ -27,7 +34,12 @@ export function withLocalization<P extends WithLocalizationProps>(
}
// Re-bind getString to trigger a re-render of Inner.
const getString = l10n.getString.bind(l10n);
return createElement(Inner, { getString, ...props } as P);
const getFormattedMessage = l10n.getFormattedMessage.bind(l10n);
return createElement(Inner, {
getString,
getFormattedMessage,
...props,
} as P);
}

WithLocalization.displayName = `WithLocalization(${displayName(Inner)})`;
Expand Down
37 changes: 37 additions & 0 deletions fluent-react/test/with_localization.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,41 @@ bar = BAR {$arg}

expect(renderer.toJSON()).toMatchInlineSnapshot(`"BAR"`);
});

test("getFormattedMessage with access to the l10n context", () => {
vi.spyOn(console, "warn").mockImplementation(() => {});
const bundle = new FluentBundle("en", { useIsolating: false });
const EnhancedComponent = withLocalization(DummyComponent);

bundle.addResource(
new FluentResource(`
foo = FOO
.bar = BAR {$arg}
`)
);

const renderer = TestRenderer.create(
<LocalizationProvider l10n={new ReactLocalization([bundle])}>
<EnhancedComponent />
</LocalizationProvider>
);

const { getFormattedMessage } =
renderer.root.findByType(DummyComponent).props;

// Returns the translation.
expect(getFormattedMessage("foo", { arg: "ARG" })).toEqual({
value: "FOO",
attributes: { bar: "BAR ARG" },
});

// It reports an error on formatting errors, but doesn't throw.
expect(getFormattedMessage("foo", {})).toEqual({
value: "FOO",
attributes: { bar: "BAR {$arg}" },
});
expect(console.warn.mock.calls).toEqual([
["[@fluent/react] ReferenceError: Unknown variable: $arg"],
]);
});
});