The Hubs redesign has a lot of new text and we need help from people like you to translate it. Follow the steps below to get started.
This script extracts all existing messages in the Hubs codebase to a temporary file, extracted-messages.json
. This file includes the message id, default message, and where the translation is used in the codebase.
This script extracts all existing messages in the Hubs codebase to a temporary file en.json
. It does not contain any metadata, just the id and English translation. It can be used as a template for other locales.
- Add an entry to
AVAILABLE_LOCALES
in locale_config.js with the appropriate locale code and in-language translation of the language. - Copy an existing locale (e.g. en.json) to a new file using appropriate locale code as filename. (e.g. zh.json)
- For locales that have duplicate codes (e.g.
zh
andzh-ch
), editFALLBACK_LOCALES
in locale_config.js to define the fallback locale so that the locale file doesn't need to be duplicated.
- For locales that have duplicate codes (e.g.
- Edit your new locale file with your translations.
Add your new message to the Hubs client in the appropriate json file in this directory. Be sure to follow the best practices guide below. The default message should be in English.
The best practices for localization are the following:
This is due to the message extraction relying on static code analysis.
Example:
import { FormattedMessage, defineMessages, useIntl } from "react-intl";
// Good
<FormattedMessage id="sign-in-modal.complete" defaultMessage="Sign in complete" />
// Bad
<FormattedMessage id={`sign-in-modal.${step}`} />
// Good
const messages = defineMessages({
prompt: {
id: "sign-in-modal.prompt",
defaultMessage: "Please sign in"
},
complete: {
id: "sign-in-modal.complete",
defaultMessage: "Sign in complete"
}
});
function SignInModal({ step }) {
const intl = useIntl();
return <p>{intl.formatMessage(messages[step])}</p>;
}
// Bad
function SignInModal({ step }) {
return <p><FormattedMessage id={`sign-in-modal.${step}`} /></p>;
}
We are not using auto-generated ids due to backwards-compatibility issues with our existing translations. As such, we need to ensure that there are no collisions. Name the translation ids by starting with the component name in dash/kebab case. If the component is split up into multiple local components and is only used in the context of a parent component, use the parent component name.
Example:
// Good
function SignInModal() {
return <FormattedMessage id="sign-in-modal.complete" defaultMessage="Sign in complete" />;
}
// Bad
function SignInModal() {
return <FormattedMessage id="sign-in.complete" defaultMessage="Sign in complete" />;
}
// Good
function ObjectListItem({ item }) {
return (
<li>
{item.name}
<button>
<FormattedMessage id="object-list.select" defaultMessage="Select Object" />
</button>
</li>
);
}
export function ObjectList({ objects }) {
return <ul>{objects.map(o => <ObjectListItem item={o} />)}</ul>;
}
Default messages will be used to provide context to translators, they are the fallback message if it is not defined for another language, and they are used as the English translation by default.
// Good
<FormattedMessage id="sign-in-modal.complete" defaultMessage="Sign in complete" />
// Bad
<FormattedMessage id={`sign-in-modal.complete`} />
// Good
const signInCompleteMessage = defineMessage({
id: "sign-in-modal.complete",
defaultMessage: "Sign in complete"
});
// Bad
const signInCompleteMessage = defineMessage({
id: "sign-in-modal.complete"
});
It is not enough to localize just the visible text. All labels, alt text, and callouts should also be localized.
<img alt={<FormattedMessage id="logo.alt-text" defaultMessage="Hubs Logo" />} src={logoUrl} />
<div aria-label={<FormattedMessage id="element.label" defaultMessage="This is a label" />}></div>
<div title={<FormattedMessage id="element.title" defaultMessage="This is a title" />}></div>
Different languages have different grammatical rules and sentence structure. Relying on string concatenation that works in English will not always make sense in another language. Use react-intl's values
prop to format messages with variables or rich formatting. If formatting gets too complicated break it up into multiple messages.
// Bad
<p>
<small>
By proceeding, you agree to the{" "}
{showTerms && (
<>
<a rel="noopener noreferrer" target="_blank" href={termsUrl}>
terms of use
</a>{" "}
</>
)}
{showTerms && showPrivacy && "and "}
{showPrivacy && (
<a rel="noopener noreferrer" target="_blank" href={privacyUrl}>
privacy notice
</a>
)}.
</small>
</p>
// Good
export function LegalMessage({ termsUrl, privacyUrl }) {
const toslink = useCallback(
chunks => (
<a rel="noopener noreferrer" target="_blank" href={termsUrl}>
{chunks}
</a>
),
[termsUrl]
);
const privacylink = useCallback(
chunks => (
<a rel="noopener noreferrer" target="_blank" href={privacyUrl}>
{chunks}
</a>
),
[privacyUrl]
);
if (termsUrl && privacyUrl) {
return (
<FormattedMessage
id="legal-message.tos-and-privacy"
defaultMessage="By proceeding, you agree to the <toslink>terms of use</toslink> and <privacylink>privacy notice</privacylink>."
values={{
toslink,
privacylink
}}
/>
);
}
if (termsUrl && !privacyUrl) {
return (
<FormattedMessage
id="legal-message.tos"
defaultMessage="By proceeding, you agree to the <toslink>terms of use</toslink>."
values={{
toslink
}}
/>
);
}
if (!termsUrl && privacyUrl) {
return (
<FormattedMessage
id="legal-message.privacy"
defaultMessage="By proceeding, you agree to the <privacylink>privacy notice</privacylink>."
values={{
privacylink
}}
/>
);
}
return null;
}
- Reference messages as objects or defineMessages keys, not ids
Message objects contain an id and a default message. Pass the whole object into a component and use
intl.formatMessage
to render it. Alternately, use adefineMessages
function to define messages with keys and use that key to reference the message.
const errorMessages = defineMessages({
unknown: {
id: "error-message.unknown",
defaultMessage: "An unknown error occured"
},
notFound: {
id: "error-message.not-found",
defaultMessage: "Page not found"
}
});
// Good
function ErrorMessage({ message }) {
const intl = useIntl();
return <code>{intl.formatMessage(message)}</code>
}
<ErrorMessage message={errorMessages.unknown} />
// Good
function ErrorMessage({ type }) {
const intl = useIntl();
return <code>{intl.formatMessage(errorMessages[type])}</code>
}
<ErrorMessage type="unknown" />