Support for multiple string context + "About" as initial example.
## Issue 4796 - i18n: Allow support for string overloading (multiple contexts) ## Approach - Minimal code and process change. - Handle on a case-by-case basis when reported by translators. - Split the affected key in the string json by appending the context. - Translators need to be aware of the new format and not translate context itself. Code is added to detect bad translations and will revert to English. Sample in json: "About --[About section in Help Page]--": "About", "About --[tab title in Channel Page]--": "About", Sample in client code: title={__('About --[About section in Help Page]--')} - "--[ ]--" was chosen as it's unique enough (unlikely for real strings to use it) and hopefully not that distracting in the client code. - In the key itself, spaces are allowed after the string (i.e. before '--[') for neatness. It will be trimmed by the system. ## First example "About" is used in 3 places: - Channel Page - Help Page - Footer (in Odysee branch) For Russian, the word "About" is "O" and is usually not used standalone, but requires something behind it. A translator said so, and seems to be the case in other sites as well. "O xxx" "O yyy" ## Other languages For other languages that are not impacted, they can just clone the same translation for each of the split keys, just like in English. ## Possible enhancement in Transifex I see that Transifex's API includes a `context` entry. It might be possible to move the context-metadata there during the upload, so translators will never see the "--[]--" messiness (it will be shown as "Context: xxx" in the Transifex GUI). I'm not sure how to test the Transifex side, so I did not investigate further.
This commit is contained in:
parent
e8e6000b13
commit
567316cfbe
4 changed files with 33 additions and 8 deletions
|
@ -88,7 +88,8 @@
|
||||||
"Find New Channels": "Find New Channels",
|
"Find New Channels": "Find New Channels",
|
||||||
"Discover New Channels": "Discover New Channels",
|
"Discover New Channels": "Discover New Channels",
|
||||||
"publishes": "publishes",
|
"publishes": "publishes",
|
||||||
"About": "About",
|
"About --[About section in Help Page]--": "About",
|
||||||
|
"About --[tab title in Channel Page]--": "About",
|
||||||
"Share Channel": "Share Channel",
|
"Share Channel": "Share Channel",
|
||||||
"Go to page:": "Go to page:",
|
"Go to page:": "Go to page:",
|
||||||
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
|
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
|
||||||
|
|
34
ui/i18n.js
34
ui/i18n.js
|
@ -21,14 +21,14 @@ function saveMessage(message) {
|
||||||
try {
|
try {
|
||||||
knownMessages = JSON.parse(fs.readFileSync(messagesFilePath, 'utf-8'));
|
knownMessages = JSON.parse(fs.readFileSync(messagesFilePath, 'utf-8'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw 'Error parsing i18n messages file: ' + messagesFilePath + ' err: ' + err;
|
throw new Error('Error parsing i18n messages file: ' + messagesFilePath + ' err: ' + err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!knownMessages[message]) {
|
if (!knownMessages[message]) {
|
||||||
const END = '--end--';
|
const END = '--end--';
|
||||||
delete knownMessages[END];
|
delete knownMessages[END];
|
||||||
knownMessages[message] = message;
|
knownMessages[message] = removeContextMetadata(message);
|
||||||
knownMessages[END] = END;
|
knownMessages[END] = END;
|
||||||
|
|
||||||
fs.writeFile(messagesFilePath, JSON.stringify(knownMessages, null, 2) + '\n', 'utf-8', err => {
|
fs.writeFile(messagesFilePath, JSON.stringify(knownMessages, null, 2) + '\n', 'utf-8', err => {
|
||||||
|
@ -53,6 +53,31 @@ function saveMessage(message) {
|
||||||
}
|
}
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
|
function removeContextMetadata(message) {
|
||||||
|
// Example string entries with context-metadata:
|
||||||
|
// "About --[About section in Help Page]--": "About",
|
||||||
|
// "About --[tab title in Channel Page]--": "About",
|
||||||
|
const CONTEXT_BEGIN = '--[';
|
||||||
|
const CONTEXT_FINAL = ']--';
|
||||||
|
|
||||||
|
// If the resolved string still contains the context-metadata, then it's one of the following:
|
||||||
|
// 1. In development mode, where 'en.json' in the server hasn't been updated with the string yet.
|
||||||
|
// 2. Translator made a mistake of not ignoring the context string.
|
||||||
|
// In either case, we'll revert to the English version.
|
||||||
|
|
||||||
|
const begin = message.lastIndexOf(CONTEXT_BEGIN);
|
||||||
|
if (begin > 0 && message.endsWith(CONTEXT_FINAL)) {
|
||||||
|
// Strip away context:
|
||||||
|
message = message.substring(0, begin);
|
||||||
|
// No trailing spaces should be allowed in the string database anyway, because that is hard to translate
|
||||||
|
// (can't see in Transifex; might not make sense in other languages; etc.).
|
||||||
|
// With that, we can add a space before the context-metadata to make it neat, and trim both cases here:
|
||||||
|
message = message.trimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
export function __(message, tokens) {
|
export function __(message, tokens) {
|
||||||
const language = localStorageAvailable
|
const language = localStorageAvailable
|
||||||
? window.localStorage.getItem('language') || 'en'
|
? window.localStorage.getItem('language') || 'en'
|
||||||
|
@ -62,9 +87,8 @@ export function __(message, tokens) {
|
||||||
saveMessage(message);
|
saveMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const translatedMessage = window.i18n_messages[language]
|
let translatedMessage = window.i18n_messages[language] ? window.i18n_messages[language][message] || message : message;
|
||||||
? window.i18n_messages[language][message] || message
|
translatedMessage = removeContextMetadata(translatedMessage);
|
||||||
: message;
|
|
||||||
|
|
||||||
if (!tokens) {
|
if (!tokens) {
|
||||||
return translatedMessage;
|
return translatedMessage;
|
||||||
|
|
|
@ -200,7 +200,7 @@ function ChannelPage(props: Props) {
|
||||||
<Tabs onChange={onTabChange} index={tabIndex}>
|
<Tabs onChange={onTabChange} index={tabIndex}>
|
||||||
<TabList className="tabs__list--channel-page">
|
<TabList className="tabs__list--channel-page">
|
||||||
<Tab disabled={editing}>{__('Content')}</Tab>
|
<Tab disabled={editing}>{__('Content')}</Tab>
|
||||||
<Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab>
|
<Tab>{editing ? __('Editing Your Channel') : __('About --[tab title in Channel Page]--')}</Tab>
|
||||||
<Tab disabled={editing}>{__('Discussion')}</Tab>
|
<Tab disabled={editing}>{__('Discussion')}</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
|
|
|
@ -212,7 +212,7 @@ class HelpPage extends React.PureComponent<Props, State> {
|
||||||
{/* @endif */}
|
{/* @endif */}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title={__('About')}
|
title={__('About --[About section in Help Page]--')}
|
||||||
subtitle={
|
subtitle={
|
||||||
this.state.upgradeAvailable !== null && this.state.upgradeAvailable ? (
|
this.state.upgradeAvailable !== null && this.state.upgradeAvailable ? (
|
||||||
<span>
|
<span>
|
||||||
|
|
Loading…
Reference in a new issue