“Localization” refers to the practice of customizing your app to support users speaking different languages, or in different places around the world. Localization is important because it gives more people access to your application.
TODO
To localize your application, you need to tell Quilt what locale your application is using. This value will be used to determine the right translations to load, how to format values, and as the lang
attribute on the HTML document.
The simplest way to set the locale is to provide a hardcoded value to Quilt’s Localization
component. Like the HTML lang
attribute, the locale
prop should be a language tag in the format defined in RFC 5646, like en
for english, or fr-CA
for french as spoken in Canada.
import {Localization} from '@quilted/quilt';
export function App() {
return (
<Localization locale="fr-CA">
<RestOfApp />
</Localization>
);
}
The browser sends a special header, Accept-Language
, that lets the server know what locale the user prefers. When using Quilt with server rendering, you can omit the locale
prop from the Localization
component, and Quilt will automatically use the locale from the Accept-Language
header on the server, and its equivalent in the browser.
import {Localization} from '@quilted/quilt';
export function App() {
return (
<Localization>
<RestOfApp />
</Localization>
);
}
You can use the useLocale
hook to access the current locale elsewhere in your React application:
import {Localization, useLocale} from '@quilted/quilt';
export function App() {
return (
<Localization locale="fr-CA">
<RestOfApp />
</Localization>
);
}
function RestOfApp() {
// `locale` will be `'fr-CA'` in this case.
const locale = useLocale();
return <p>It looks like your locale is: {locale}</p>;
}
One strategy that is commonly used on the web is to provide different locales at different URLs. Quilt also provides a full routing library, and gives you some handy utilities for common versions of this technique.
In path-based localization, the first part of the URL’s path is used to determine the locale of the application. For example, /en/contact
and /fr-ca/contact
would both be the same contact page, but in en
and fr-CA
locales, respectively.
To implement localized routing, you’ll use the LocalizedRouter
component provided by Quilt. You will pass this component the information it needs to localize your app using the createRoutePathLocalization
helper function, which takes in the information about what locales are supported in your app.
import {
useLocale,
useRoutes,
Localization,
createRoutePathLocalization,
} from '@quilted/quilt';
const routeLocalization = createRoutePathLocalization({
default: 'en',
locales: ['en', 'fr', 'fr-CA'],
});
export function App() {
return (
<LocalizedRouter localization={routeLocalization}>
<Routes />
</LocalizedRouter>
);
}
function Routes() {
const locale = useLocale();
return useRoutes([
{match: '/', render: () => <div>{/* home page */}</div>},
{
match: 'contact',
render: () => <div>{/* contact page */}</div>,
},
]);
}
Note: If you already have a
Router
component rendered in your application, make sure you replace it with thisLocalizedRouter
component — you don’t need both. You also don’t need to manually render theLocalization
component when usingLocalizedRouter
.
In the example above, /en
and /en/contact
would be rendered with an english locale, while /fr
, /fr/contact
, /fr-ca
, and /fr-ca/contact
would be rendered using french and french Canadian locales.
By default, the default locale is also included in the path, as shown above with the /en
and /en/contact
pages. If you would prefer to have the root locale be at the “root” of your app, you can pass the {nested: false}
option alongside the default locale:
import {
useLocale,
useRoutes,
Localization,
createRoutePathLocalization,
} from '@quilted/quilt';
const routeLocalization = createRoutePathLocalization({
default: {locale: 'en', nested: false},
locales: ['en', 'fr', 'fr-CA'],
});
export function App() {
return (
<LocalizedRouter localization={routeLocalization}>
<Routes />
</LocalizedRouter>
);
}
In the example above, the english pages would now be available at /
and /contact
.
When using LocalizedRouter
, the locale of your application will be set to the same value as the initial request’s locale
(determined by the useBrowserDetails().locale
value), as long as that locale is at least in the same language as the resolved route-based locale. In the examples above, if a user visited the app with an en-CA
locale, that locale would be preserved, even though the route only matches the en
language.
In subdomain-based localization, the part of the URL is used to determine the locale of the application. For example, en.my-app.com
and fr-ca.my-app.com
would both be the same contact page, but in en
and fr-CA
locales, respectively.
To implement localized routing, you’ll use the LocalizedRouter
component provided by Quilt. You will pass this component the information it needs to localize your app using the createRouteSubdomainLocalization
helper function, which takes in the information about what locales are supported in your app.
import {
useLocale,
useRoutes,
Localization,
createRoutePathLocalization,
} from '@quilted/quilt';
const routeLocalization = createRoutePathLocalization({
base: 'app.com',
default: 'en',
locales: ['en', 'fr'],
});
export function App() {
return (
<LocalizedRouter localization={routeLocalization}>
<Routes />
</LocalizedRouter>
);
}
function Routes() {
const locale = useLocale();
return useRoutes([
{match: '/', render: () => <div>{/* home page */}</div>},
{
match: 'contact',
render: () => <div>{/* contact page */}</div>,
},
]);
}
Note: If you already have a
Router
component rendered in your application, make sure you replace it with thisLocalizedRouter
component — you don’t need both. You also don’t need to manually render theLocalization
component when usingLocalizedRouter
.
In the example above, en.app.com
and en.app.com/contact
would be rendered with an english locale, while fr.app.com
and fr.app.com/contact
would be rendered using a french locale.
By default, the default locale is also included in the subdomain, as shown above with the en.app.com
subdomain. If you would prefer to have the root locale be at the “root” of your app, you can pass the {nested: false}
option alongside the default locale:
import {
useLocale,
useRoutes,
Localization,
createRoutePathLocalization,
} from '@quilted/quilt';
const routeLocalization = createRoutePathLocalization({
base: 'app.com',
default: {locale: 'en', nested: false},
locales: ['en', 'fr', 'fr-CA'],
});
export function App() {
return (
<LocalizedRouter localization={routeLocalization}>
<Routes />
</LocalizedRouter>
);
}
In the example above, the english pages would now be available at app.com
.
When using LocalizedRouter
, the locale of your application will be set to the same value as the initial request’s locale
(determined by the useBrowserDetails().locale
value), as long as that locale is at least in the same language as the resolved route-based locale. In the examples above, if a user visited the app with an en-CA
locale, that locale would be preserved, even though the route only matches the en
language.
TODO
Quilt provides a useLocalizedFormatting
hook that gives you access to functions capable of localizing values according to the app’s locale. This hook provides access to three functions, each useful for formatting a different kind of value for the user:
formatNumber()
formats a number. You can pass this function any option you can pass toIntl.NumberFormat
, which is used under the hood.formatCurrency()
formats a number as a currency value. You can also pass this function any option you can pass toIntl.NumberFormat
.formatDate()
formats aDate
object. You can pass this function any option you can pass toIntl.DateTimeFormat
, which is used under the hood.
import {Localization, useLocalizedFormatting} from '@quilted/quilt';
export function App() {
return (
<Localization locale="en-CA">
<RestOfApp />
</Localization>
);
}
const purchase = {
date: new Date(),
quantity: 2,
cost: 30,
};
function RestOfApp() {
const {formatNumber, formatCurrency, formatDate} = useLocalizedFormatting();
return (
<dl>
<dt>Purchased on</dt>
<dd>{formatDate(purchase.date, {dateStyle: 'short'})}</dd>
<dt>Quantity</dt>
<dd>{formatNumber(purchase.quantity)}</dd>
<dt>Cost</dt>
<dd>{formatCurrency(purchase.cost, {currency: 'CAD'})}</dd>
</dl>
);
}