Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile-time issues with React tutorial #537

Open
sandalwoodbox opened this issue Apr 3, 2021 · 4 comments
Open

Compile-time issues with React tutorial #537

sandalwoodbox opened this issue Apr 3, 2021 · 4 comments

Comments

@sandalwoodbox
Copy link

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.

@sandalwoodbox
Copy link
Author

This is not a compile-time error, but the tutorial also leaves me with:

Failed prop type: The prop l10n is marked as required in LocalizationProvider, but its value is undefined.

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

@sandalwoodbox
Copy link
Author

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

@sandalwoodbox
Copy link
Author

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;

@ManuelTS
Copy link

ManuelTS commented Feb 7, 2022

@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:

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants