fluent.js
fluent.js copied to clipboard
Compile-time issues with React tutorial
In https://github.com/projectfluent/fluent.js/wiki/React-Tutorial, I had to make the following adjustments to the final generateBundles function to get it to compile:
Current:
// Generate bundles for each locale.
async function generateBundles() {
return languages.map(locale => {
const translations = await getMessages(locale);
const bundle = new FluentBundle(locale);
bundle.addMessages(translations);
return bundle;
});
}
Fixed:
// Generate bundles for each locale.
async function generateBundles() {
return languages.map(async (locale) => {
const translations = await getMessages(locale);
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(translations));
return bundle;
});
}
At first I was getting Unexpected reserved word 'await' for getMessages; I fixed that by marking the map call as async. I then had to update addMessages to addResource because addMessages doesn't exist.
This is not a compile-time error, but the tutorial also leaves me with:
Failed prop type: The prop
l10nis marked as required inLocalizationProvider, but its value isundefined.
It looks like I need to find a way to merge the final tutorial output with the ReactLocalization usage here: https://github.com/projectfluent/fluent.js/wiki/React-Bindings
It looks like the ReactLocalization class requires synchronicity, since it only supports CachedSyncIterable / mapBundleSync: https://github.com/projectfluent/fluent.js/blob/master/fluent-react/src/localization.ts#L25
If anyone else runs into this, I was able to work around it by wrapping LocalizationProvider in a component that handles loading the bundles asynchronously.
import React from "react";
import "intl-pluralrules";
import { negotiateLanguages } from "@fluent/langneg";
import { FluentBundle, FluentResource } from "@fluent/bundle";
import { LocalizationProvider, ReactLocalization } from "@fluent/react";
import PropTypes from "prop-types";
const AVAILABLE_LOCALES = ["en-US", "zh-CN"];
// Negotiate user language.
const languages = negotiateLanguages(navigator.languages, AVAILABLE_LOCALES, {
defaultLocale: "en-US",
});
// Load locales from files.
async function getMessages(locale) {
const url = `/static/locale/${locale}/content.ftl`;
const response = await fetch(url);
return await response.text();
}
// Generate bundles for each locale.
async function generateBundles() {
return languages.map(async (locale) => {
const translations = await getMessages(locale);
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(translations));
return bundle;
});
}
class BundleLoader extends React.Component {
constructor(props) {
super(props);
this.state = {
l10n: new ReactLocalization([]),
};
}
componentDidMount() {
generateBundles().then((bundlePromises) => {
Promise.all(bundlePromises).then((bundles) => {
console.log(bundles)
this.setState({ l10n: new ReactLocalization(bundles) });
})
});
}
render() {
return (
<LocalizationProvider l10n={this.state.l10n}>
{this.props.children}
</LocalizationProvider>
);
}
}
BundleLoader.propTypes = {
children: PropTypes.element.isRequired,
};
export default BundleLoader;
@sandalwoodbox thanks for you code, it helped a lot! I had to remove staticin your URL to make it work. @Pike or @stasm, I did the setup in Typescript within my App component and I think its parts or the whole below code would fit nicely into the following wiki pages:
- https://github.com/projectfluent/fluent.js/wiki/React-Bindings
- Especially: https://github.com/projectfluent/fluent.js/wiki/React-Bindings#a-complete-example
- https://github.com/projectfluent/fluent.js/wiki/React-Tutorial
Here the TS code:
import React, {ReactElement} from 'react';
import './App.css';
import ChatContainer from './components/chat-container/ChatContainer';
import {FluentBundle, FluentResource} from '@fluent/bundle';
import {negotiateLanguages} from '@fluent/langneg';
import {LocalizationProvider, ReactLocalization} from '@fluent/react';
interface State {
l10n: ReactLocalization;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export default class App extends React.Component<{}, State> {
// eslint-disable-next-line @typescript-eslint/ban-types
constructor(props: {}) {
super(props);
this.state = { l10n: new ReactLocalization([])};
}
// Load locales async from files
private readonly getMessages = (locale: string): Promise<string> =>
fetch(`/locales/${locale}.ftl`) // Must be in your public dir
.then((response: Response) => response.text())
.catch(e => {
console.error(e);
return '';
});
// Generate bundles for each locale
private readonly generateBundles = (userLocales: string[]): Promise<FluentBundle[]> => {
// Choose locales that are best for the user.
const currentLocales = negotiateLanguages(
userLocales,
['de-DE'], // You know why only German is allowed here ;)
{ defaultLocale: 'de-DE' }
);
return Promise.all(currentLocales.map(async (locale: string) =>
this.getMessages(locale)
.then((translations: string) => {
const bundle: FluentBundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(translations));
return bundle;
})));
};
componentDidMount(): void {
this.generateBundles(navigator.languages as string[])
.then((fluentBundles: FluentBundle[]) => {
this.setState({ l10n: new ReactLocalization(fluentBundles) });
})
.catch(e => console.error(e));
}
render = (): ReactElement =>
// @ts-ignore
<LocalizationProvider l10n={this.state.l10n}>
// The below code works also in all nested components, try it!
<Localized id={'your-.ftl-file-key'}>
your-.ftl-file-translation-value
</Localized>
</LocalizationProvider>;
}
I tried to find how I can update the wiki pages with the gh-pages branch, but editing the raw HTML seams to be an overhead. Is there another way?