@hi18n/react
Version:
Message internationalization meets immutability and type-safety - runtime for React
313 lines (281 loc) • 8.2 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "LocaleContext", {
enumerable: true,
get: function () {
return _reactContext.LocaleContext;
}
});
exports.LocaleProvider = void 0;
exports.Translate = Translate;
exports.useI18n = useI18n;
exports.useLocales = useLocales;
var _react = _interopRequireDefault(require("react"));
var _reactContext = require("@hi18n/react-context");
var _core = require("@hi18n/core");
/**
* Renders the children with the specified locale.
*
* @since 0.1.0 (`@hi18n/react`)
*
* @example
* ```tsx
* ReactDOM.render(
* root,
* <LocaleProvider locales="ja">
* <Translate id="example/greeting" book={book} />
* </LocaleProvider>
* );
* ```
*/
const LocaleProvider = props => {
const {
locales,
children
} = props;
const concatenatedLocales = Array.isArray(locales) ? locales.join("\n") : locales;
return /*#__PURE__*/_react.default.createElement(_reactContext.LocaleContext.Provider, {
value: concatenatedLocales
}, children);
};
/**
* Returns the locales from the context.
*
* @returns A list of locales in the order of preference.
*
* @since 0.1.2 (`@hi18n/react`)
*
* @example
* ```tsx
* const Greeting: React.FC = () => {
* const { t } = useI18n(book);
* return (
* <section>
* <h1>{t("example/greeting")}</h1>
* {
* messages.length > 0 &&
* <p>{t("example/messages", { count: messages.length })}</p>
* }
* </section>
* );
* };
* ```
*/
exports.LocaleProvider = LocaleProvider;
function useLocales() {
const localesConcat = _react.default.useContext(_reactContext.LocaleContext);
const locales = _react.default.useMemo(() => localesConcat === "" ? [] : localesConcat.split("\n"), [localesConcat]);
return locales;
}
/**
* Retrieves translation helpers, using the locale from the context.
*
* If the catalog is not loaded yet, it suspends the component being
* rendered. This is an **experimental API** which relies on React's
* undocumented API for suspension.
* To avoid this behavior,
* initialize the Book statically or use preloadCatalog from @hi18n/core
* to ensure the catalog is loaded before using this function.
*
* @param book A "book" object containing translated messages
* @returns An object containing functions necessary for translation
*
* @since 0.1.0 (`@hi18n/react`)
*
* @example
* ```tsx
* const Greeting: React.FC = () => {
* const { t } = useI18n(book);
* return (
* <section>
* <h1>{t("example/greeting")}</h1>
* {
* messages.length > 0 &&
* <p>{t("example/messages", { count: messages.length })}</p>
* }
* </section>
* );
* };
* ```
*/
function useI18n(book) {
const locales = useLocales();
const i18n = _react.default.useMemo(() => (0, _core.getTranslator)(book, locales, {
throwPromise: true
}), [book, locales]);
return i18n;
}
/**
* Renders the translated message, possibly interleaved with the elements you provide.
*
* If the catalog is not loaded yet, it suspends the component being
* rendered. This is an **experimental API** which relies on React's
* undocumented API for suspension.
* To avoid this behavior,
* initialize the Book statically or use preloadCatalog from @hi18n/core
* to ensure the catalog is loaded before rendering this component.
*
* @since 0.1.0 (`@hi18n/react`)
*
* @example
* ```tsx
* <Translate id="example/signin" book={book}>
* {
* // These elements are inserted into the translation.
* }
* <a href="" />
* <a href="" />
* </Translate>
* ```
*
* @example You can add a placeholder for readability.
* ```tsx
* <Translate id="example/signin" book={book}>
* You need to <a href="">sign in</a> or <a href="">sign up</a> to continue.
* </Translate>
* ```
*
* @example Naming the elements
* ```tsx
* <Translate id="example/signin" book={book}>
* <a key="signin" href="" />
* <a key="signup" href="" />
* </Translate>
* ```
*
* @example to supply non-component parameters, you can:
* ```tsx
* <Translate id="example/greeting" book={book} name={name} />
* ```
*
* This is almost equivalent to the following:
* ```tsx
* const { t } = useI18n(book);
* return t("example/greeting", { name });
* ```
*/
function Translate(props) {
const {
book,
id,
children,
renderInElement,
...params
} = props;
extractComponents(children, params, {
length: 0
});
fillComponentKeys(params);
const translator = useI18n(book);
const interpolator = getInterpolator();
const translatedChildren = translator.translateWithComponents(id, interpolator, // eslint-disable-next-line @typescript-eslint/no-explicit-any
params);
if (renderInElement) {
return /*#__PURE__*/_react.default.cloneElement(renderInElement, {}, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, translatedChildren));
} else {
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, translatedChildren);
}
}
/**
* A variant of {@link Translate} for dynamic translation keys
*
* @since 0.1.1 (`@hi18n/react`)
*
* @example
* ```tsx
* const id = translationId(book, "example/signin");
* <Translate.Dynamic id={id} book={book}>
* <a href="" />
* <a href="" />
* </Translate.Dynamic>
* ```
*/
// eslint-disable-next-line @typescript-eslint/ban-types
Translate.Dynamic = Translate;
/**
* A variant of {@link Translate} for translation bootstrap.
*
* At runtime, it just renders a TODO text.
*
* @since 0.1.1 (`@hi18n/react`)
*
* @example
* ```tsx
* <Translate.Todo id="example/message-to-work-on" book={book}>
* </Translate.Todo>
* ```
*/
Translate.Todo = function Todo(props) {
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "[TODO: ", props.id, "]");
}; // <Translate>foo<a/> <strong>bar</strong> </Translate> => { 0: <a/>, 1: <strong/> }
// <Translate><strong><em></em></strong></Translate> => { 0: <strong/>, 1: <em/> }
// <Translate><a key="foo" /> <button key="bar" /></Translate> => { foo: <a/>, bar: <button/> }
function extractComponents(node, params, state) {
if ( /*#__PURE__*/_react.default.isValidElement(node)) {
if (node.key != null) {
params[node.key] = /*#__PURE__*/_react.default.cloneElement(node, {
key: node.key
});
} else {
params[state.length] = /*#__PURE__*/_react.default.cloneElement(node, {
key: state.length
});
state.length++;
}
extractComponents(node.props.children, params, state);
} else if (Array.isArray(node)) {
for (const child of node) {
extractComponents(child, params, state);
}
}
}
function fillComponentKeys(params) {
for (const [key, value] of Object.entries( // eslint-disable-next-line @typescript-eslint/ban-types
params)) {
if (! /*#__PURE__*/_react.default.isValidElement(value)) continue;
if (value.key == null) {
params[key] = /*#__PURE__*/_react.default.cloneElement(value, {
key
});
}
}
}
function getInterpolator() {
const keys = {};
function generateKey(key) {
if (!hasOwn(keys, key)) {
Object.defineProperty(keys, key, {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
}
const id = keys[key]++;
if (id === 1 && !/\$/.test(key)) {
return key;
} else {
return "".concat(key, "$").concat(id);
}
}
function collect(submessages) {
return submessages;
}
function wrap(component, message) {
const newKey = generateKey("".concat(component.key));
return /*#__PURE__*/_react.default.cloneElement(component, {
key: newKey
}, message);
}
return {
collect,
wrap
};
}
function hasOwn(o, s) {
return Object.prototype.hasOwnProperty.call(o, s);
}
//# sourceMappingURL=index.js.map