-
Notifications
You must be signed in to change notification settings - Fork 80
Expand file tree
/
Copy pathlocalized.js
More file actions
134 lines (114 loc) · 3.43 KB
/
Copy pathlocalized.js
File metadata and controls
134 lines (114 loc) · 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { isValidElement, cloneElement, Component, Children } from 'react';
import PropTypes from 'prop-types';
import { MessageArgument } from 'fluent/compat';
import { isReactLocalization } from './localization';
/*
* A Fluent argument type for React elements.
*
* When `MessageContext`'s `formatToParts` method is used, any interpolations
* which are valid `MessageArgument` instances are returned unformatted. The
* parts can then be `valueOf`'ed and concatenated to create the final
* translation.
*
* With `ElementArgument` it becomes possible to pass React elements as
* arguments to translations. This may be useful for passing links or buttons,
* or in general: elements with logic which should be defined in the app.
*/
class ElementArgument extends MessageArgument {
valueOf() {
return this.value;
}
}
/*
* Prepare props passed to `Localized` for formetting.
*
* Filter props which are not intended for formatting and turn arguments which
* are React elements into `ElementArgument` instances.
*
*/
function toArguments(props) {
const args = {};
for (const propname of Object.keys(props)) {
if (!propname.startsWith('$')) {
continue;
}
const arg = props[propname];
const name = propname.substr(1);
if (isValidElement(arg)) {
args[name] = new ElementArgument(arg);
} else {
args[name] = arg;
}
}
return args;
}
/*
* The `Localized` class renders its child with translated props and children.
*
* <Localized id="hello-world">
* <p>{'Hello, world!'}</p>
* </Localized>
*
* The `id` prop should be the unique identifier of the translation. Any
* attributes found in the translation will be applied to the wrapped element.
*
* Arguments to the translation can be passed as `$`-prefixed props on
* `Localized`.
*
* <Localized id="hello-world" $username={name}>
* <p>{'Hello, { $username }!'}</p>
* </Localized>
*
* It's recommended that the contents of the wrapped component be a string
* expression. The string will be used as the ultimate fallback if no
* translation is available. It also makes it easy to grep for strings in the
* source code.
*/
export default class Localized extends Component {
componentDidMount() {
const { l10n } = this.context;
if (l10n) {
l10n.subscribe(this);
}
}
componentWillUnmount() {
const { l10n } = this.context;
if (l10n) {
l10n.unsubscribe(this);
}
}
/*
* Rerender this component in a new language.
*/
relocalize() {
// When the `ReactLocalization`'s fallback chain changes, update the
// component.
this.forceUpdate();
}
render() {
const { l10n } = this.context;
const { id, children } = this.props;
const elem = Children.only(children);
if (!l10n) {
// Use the wrapped component as fallback.
return elem;
}
const mcx = l10n.getMessageContext(id);
if (mcx === null) {
// Use the wrapped component as fallback.
return elem;
}
const msg = mcx.getMessage(id);
const args = toArguments(this.props);
const { parts, attrs } = l10n.formatCompound(mcx, msg, args);
// The formatted parts can be passed to `cloneElements` as arguments. They
// will be used as children of the cloned element.
return cloneElement(elem, attrs, ...parts);
}
}
Localized.contextTypes = {
l10n: isReactLocalization
};
Localized.propTypes = {
children: PropTypes.element.isRequired,
};