32.0.0 • Published 3 months ago

tachyon-intl v32.0.0

Weekly downloads
-
License
UNLICENSED
Repository
-
Last release
3 months ago

Tachyon-Intl

Internationalization helpers using twitch-intl that works well with tachyon-intl-server.

Setup

Add twitch-intl-cli To Your Project

  1. If you have not yet had your Smartling project onboarded to Loom, reach out on Slack in the #loom channel.

  2. In the package's package.json file add the necessary intl commands and dependencies, replacing $LOOM_PROJECT with your Loom project id:

    {
      "files": ["messages/"],
      "scripts": {
        "intl:download": "twitch-intl download --midway -S \"!(node_modules)/**/!(*.test).ts?(x)\" --project-id $LOOM_PROJECT --outdir ./messages",
        "intl:submit": "twitch-intl upload --midway -S \"!(node_modules)/**/!(*.test).ts?(x)\" --project-id $LOOM_PROJECT --prompt --exclude-uploaded --submit --jira-ticket",
        "intl:test": "yarn intl:test:ci --midway",
        "intl:test:ci": "twitch-intl submission-check -S \"!(node_modules)/**/!(*.test).ts?(x)\" --project-id $LOOM_PROJECT"
      },
      "dependencies": {
        "tachyon-intl": "^17.0.0"
      },
      "devDependencies": {
        "twitch-intl-cli": "^4.4.0"
      }
    }
  3. Create a messages directory in the root of the project.

  4. Submit strings following the submission process.

  5. Run yarn intl:download to populate messages for all locales.

Install The Root Context

Your app can either load intl data on its own and pass them into TachyonIntlRoot (this is what Tachyon apps do):

import type { FC } from 'react';
import { IntlData, TachyonIntlRoot } from 'tachyon-intl';

export const renderApp: FC = () => {
  // Implement getIntlData reading it in the way that makes the most sense
  // for your application
  const intlData: IntlData = getIntlData();

  return <TachyonIntlRoot data={intlData}>{/* App JSX */}</TachyonIntlRoot>;
};

If your app already uses TwitchIntl and needs to inter-op with tachyon-intl, you can pass in the intl instance (this is what Twilight does):

import type { FC } from 'react';
import { TachyonIntlRoot } from 'tachyon-intl';

export const renderApp: FC = () => {
  // Wherever your intl comes from
  const intl = createTwitchIntl();

  return <TachyonIntlRoot intl={intl}>{/* App JSX */}</TachyonIntlRoot>;
};

Submitting Strings

Tachyon-Intl uses twitch-intl-cli to manage string submission. Submit strings for the "tachyon" (if developing in Tachyon) Loom project (corresponding to the "Mobile Web" Smartling project) following the string submission process.

Downloading Strings

yarn intl:download

Formatting App Strings For Internationalization

Reference this internationalization guide for an exhaustive list of localization use cases such as dealing with numbers, dates, times, durations, and link rendering in sentences.

Hook Use

  1. Call useIntl() in your functional component.
  2. Destructure the necessary format helper(s) to a local variable named formatMessage (or one of the other names) off of the return of useIntl().
  3. Read the documentation for using these helpers.

IMPORTANT: Do not skip step #2. If you attempt to consume formatMessage directly off of intl, twitch-intl-cli will not function correctly. The internationalization relies on the names matching exactly and being their own function names.

import type { FC } from 'react';
import { useIntl } from 'tachyon-intl';

interface WelcomeBannerProps {}

export const WelcomeBanner: FC<WelcomeBannerProps> = (props) => {
  // Always assign to a local variable before consuming
  const { formatMessage, formatNumber, formatNumberShort } = useIntl();

  return (
    <div>
      {formatMessage('Welcome to your home screen!', 'WelcomeBanner')}
      {/* ... */}
    </div>
  );
};

HOC Use

  1. Wrap a component with the withIntl HOC and extend IntlProps in the component's prop interface.
  2. Assign the necessary format helper(s) to a local variable named formatMessage (or one of the other names) off of props.intl.
  3. Read the documentation for using these helpers.

IMPORTANT: Do not skip step #2. If you attempt to consume formatMessage directly off of props.intl, twitch-intl-cli will not function correctly. The internationalization relies on the names matching exactly and being their own function names.

import { Component } from 'react';
import { IntlProps, withIntl } from 'tachyon-intl';

interface WelcomeBannerProps extends IntlProps {}

class WelcomeBannerBase extends Component<WelcomeBannerProps> {
  public override render(): JSX.Element {
    // Always assign to a local variable before consuming
    const { formatMessage, formatNumber, formatNumberShort } = this.props.intl;

    return (
      <div>
        {formatMessage('Welcome to your home screen!', 'WelcomeBanner')}
        {/* ... */}
      </div>
    );
  }
}

export const WelcomeBanner = withIntl(WelcomeBannerBase);

Getting The User's Selected Locale

You can also get the user's preferred, among those we support, locale and language:

import type { FC } from 'react';
import { useIntl } from 'tachyon-intl';

const LocaleDependentComponent: FC = () => {
  const { getActiveLocale, getLanguageCode } = useIntl();
  // ...
};

Gating Features Based On User Locale

The <LocaleGate> allows gating features behind a list of country codes. By default it operates in allowlist mode but it can also operate as a blocklist.

Operating as an Allowlist

import type { FC } from 'react';
import { LocaleGate, CountryCode } from 'tachyon-intl';

const ComponentThatShouldOnlyRenderContentInBrazilAndMexico: FC = () => {
  return (
    <LocaleGate countries={[CountryCode.Brazil, CountryCode.Mexico]}>
      {/*Brazil & Mexico only content here*/}
    </LocaleGate>
  );
};

By default <LocaleGate> will fall back to rendering null. You can optionally pass a fallback.

Operating as a Allowlist with Fallback

import type { FC } from 'react';
import { LocaleGate, CountryCode } from 'tachyon-intl';

const FallBackComponent: FC = () => {
  return (
    <div>
      This is only available in Brazil & Mexico and you sadly aren't there.
    </div>
  );
};

const ComponentThatShouldOnlyRenderContentInBrazilAndMexico: FC = () => {
  return (
    <LocaleGate
      countries={[CountryCode.Brazil, CountryCode.Mexico]}
      fallback={FallBackComponent} // you can also write this as () => <FallbackComponent />
    >
      {/* Brazil & Mexico only content here */}
    </LocaleGate>
  );
};

Operating as a Blocklist

import type { FC } from 'react';
import { LocaleGate, CountryCode } from 'tachyon-intl';

const ComponentThatShouldOnlyRenderContentInBrazilAndMexico: FC = () => {
  return (
    <LocaleGate blocklist countries={[CountryCode.Brazil, CountryCode.Mexico]}>
      {/* Globally accessible content EXCEPT for Brazil & Mexico */}
    </LocaleGate>
  );
};

Warnings

  • It's very easy to run into surprising issues by rendering content on the server that you assumed would happen on the client. Particularly, the formatDate and formatTime methods will probably not behave as you expect when running in a Server Side Rendered application. For mitigation and more details see dates.
  • When using <LocaleGate> be aware that this uses the user's device locale as a trigger. The locale is in the user's control on their device (they can change it by changing their device country & language setting), so do not use this for sensitive features!

Common Issues

You are required to have a valid mwinit cookie to run intl:download and intl:submit commands. If you aren't you'll see something like:

Downloading latest translated messages...
There was an error downloading the messages.
Error getting strings for project tachyon. Midway/ApiGateway response: 403 Forbidden
Have you run mwinit recently?
Done, with errors. Using --debug may help diagnose issues.