Skip to content

Commit 16013c8

Browse files
committed
(@fluent/react) Add useTranslate
1 parent cff95f6 commit 16013c8

File tree

3 files changed

+107
-16
lines changed

3 files changed

+107
-16
lines changed

fluent-react/example/src/Hello.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
import React, { useState } from "react";
2-
import { Localized } from "@fluent/react";
2+
import { useTranslate } from "@fluent/react";
33

44
export function Hello() {
55
let [userName, setUserName] = useState("");
6+
const { t, tAttributes } = useTranslate()
67

78
return (
89
<div>
910
{userName ?
10-
<Localized id="hello" vars={{ userName }}>
11-
<h1>{'Hello, { $userName }!'}</h1>
12-
</Localized>
13-
:
14-
<Localized id="hello-no-name">
15-
<h1>Hello, stranger!</h1>
16-
</Localized>
11+
<h1>{t('hello', { userName })}</h1>
12+
: <h1>{t('hello-no-name')}</h1>
1713
}
1814

19-
<Localized id="type-name" attrs={{ placeholder: true }}>
20-
<input
21-
type="text"
22-
placeholder="Type your name"
23-
onChange={evt => setUserName(evt.target.value)}
24-
value={userName}
25-
/>
26-
</Localized>
15+
<input
16+
type="text"
17+
placeholder={tAttributes('type-name').placeholder}
18+
onChange={evt => setUserName(evt.target.value)}
19+
value={userName}
20+
/>
2721
</div>
2822
);
2923
}

fluent-react/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export { LocalizationProvider } from "./provider";
2222
export { withLocalization, WithLocalizationProps } from "./with_localization";
2323
export { Localized, LocalizedProps } from "./localized";
2424
export { MarkupParser } from "./markup";
25+
export { useTranslate } from "./useTranslate";

fluent-react/src/useTranslate.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { useContext } from "react";
2+
import { FluentContext } from "./context";
3+
import { ReactLocalization } from "./localization";
4+
import { FluentArgument, FluentBundle } from "@fluent/bundle";
5+
6+
type Message = Exclude<ReturnType<FluentBundle["getMessage"]>, undefined>
7+
8+
// Using ReactLocalization and a id, return its bundle and message object
9+
// if both exists, otherwise return null for boths
10+
type getBundleAndMessage = (
11+
(l10n: ReactLocalization, id: string) =>
12+
({ bundle: FluentBundle; msg: Message } | { bundle: null; msg: null })
13+
)
14+
const getBundleAndMessage: getBundleAndMessage = (l10n, id) => {
15+
const bundle = l10n.getBundle(id);
16+
const msg = bundle?.getMessage(id);
17+
18+
if (bundle === null || msg === undefined) {
19+
return { bundle: null, msg: null };
20+
}
21+
22+
return { bundle, msg };
23+
};
24+
25+
type TFunction = (id: string, vars?: Record<string, FluentArgument>) => string
26+
type TAttributesFunction = (
27+
(id: string, vars?: Record<string, FluentArgument>) =>
28+
{ [key in string]: string }
29+
)
30+
31+
type useTranslate = () => { t: TFunction; tAttributes: TAttributesFunction }
32+
33+
/*
34+
* The `useTranslate` hook uses the FluentContext to return two functions
35+
* to get the translated messages from Fluent's bundle:
36+
* - t: get the root message from the given id
37+
* - tAttributes: get the attributes message from the given id
38+
*/
39+
export const useTranslate: useTranslate = () => {
40+
const l10n = useContext(FluentContext);
41+
42+
if (!l10n) {
43+
// Return fallbacks functions
44+
const t: TFunction = () => "";
45+
const tAttributes: TAttributesFunction = () => ({});
46+
47+
return { t, tAttributes };
48+
}
49+
50+
const t: TFunction = (id, vars) => {
51+
const { bundle, msg } = getBundleAndMessage(l10n, id);
52+
53+
if (bundle === null || msg === null) {
54+
// Return as fallback an empty string.
55+
return "";
56+
}
57+
58+
// Format the message and return that
59+
const errors: Array<Error> = [];
60+
const msgValue = msg.value || "";
61+
const messageFormatted = bundle.formatPattern(msgValue, vars, errors);
62+
63+
errors.forEach(error => l10n.reportError(error));
64+
65+
return messageFormatted;
66+
};
67+
68+
const tAttributes: TAttributesFunction = (id, vars) => {
69+
const { bundle, msg } = getBundleAndMessage(l10n, id);
70+
71+
if (bundle === null || msg === null) {
72+
// Return as fallback an empty object.
73+
return {};
74+
}
75+
76+
// Format all atributes and return that
77+
const errors: Array<Error> = [];
78+
const messagesFormatted = Object
79+
.keys(msg.attributes)
80+
.reduce(
81+
(acc, key) => {
82+
acc[key] = bundle.formatPattern(msg.attributes[key], vars, errors);
83+
return acc;
84+
},
85+
{} as { [key in string]: string }
86+
);
87+
88+
errors.forEach(error => l10n.reportError(error));
89+
90+
return messagesFormatted;
91+
};
92+
93+
return { t, tAttributes };
94+
};
95+
96+
export default useTranslate;

0 commit comments

Comments
 (0)