Trans component does not render translation without i18nKey attribute - react-i18next

I have an app using the Trans component to perform translations. I do not declare the i18nKey attribute on my Trans component and just use the content inside the Trans component to generate the key, yet even though a key exists, Trans will not produce a translation unless i18nKey is present on the component. The t function from useTranslation correctly translates its arguments, yet the Trans component will not translate to french in this example. Full project on github here Example in my app:
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import URLSearchParams from '#ungap/url-search-params';
import 'web/js/sentry';
import 'react-toastify/dist/ReactToastify.css';
import { Root } from 'web/js/page/root';
import i18n from 'common/i18n';
if (!window.URLSearchParams) {
window.URLSearchParams = URLSearchParams;
}
ReactDOM.render(
<I18nextProvider i18n={i18n}>
<Root />
</I18nextProvider>,
document.querySelector('.projectroot'),
);
topic/hangul/index.jsx
import React from 'react';
import { Trans } from 'react-i18next';
import { Utterance } from 'web/js/application-hook/utterance';
import './style.scss';
export function Hangul() {
return (
<div styleName='root'>
<section styleName='section'>
<Trans>
<Utterance text='한글'>한글</Utterance> and <Utterance text='조선글'>조선글</Utterance> are the respective names of the contemporary Korean writing system used in South Korea and North Korea.
</Trans>
<Trans>
<Utterance text='훈민정음'>훈민정음</Utterance>, the original name of the writing system, was introduced and promoted in 1446 by Sejong the Great.
</Trans>
</section>
</div>
);
}
common/i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import fr from 'common/i18n/fr/translation.json';
import { SUPPORTED_LANGUAGE_IDS } from 'common/models';
i18n
.use(initReactI18next)
.init({
preload: SUPPORTED_LANGUAGE_IDS,
fallbackLng: 'en',
initImmediate: false,
returnEmptyString: false,
resources: {
fr: {
translation: fr
}
},
interpolation: {
escapeValue: false
},
react: {
hashTransKey: function(defaultValue) {
console.log('missing key', defaultValue);
return defaultValue;
},
}
});
export default i18n;
common/i18n/fr/translation.json
{
"The Korean Writing System": "Le système d'écriture coréen",
"<0>한글</0> and <2>조선글</2> are the respective names of the contemporary Korean writing system used in South Korea and North Korea.": "<0>한글</0> et <2>조선글</2> sont les noms respectifs du système d'écriture coréen contemporain utilisé en Corée du Sud et en Corée du Nord.",
"<0>훈민정음</0>, the original name of the writing system, was introduced and promoted in 1446 by Sejong the Great.": "<0>훈민정음</0>, le nom original du système d'écriture, a été introduit et promu en 1446 par Sejong le Grand."
}

I found out why this is happening, in the configuration options for i18next the default keySeparator is ., so all of my text which contained a . character did not properly calculate the translation due to the . acting as a keySeperator.

Related

Missing keys issue in i18next with only one React Component

I am developing a react-project with multilingual (en and fr). Everything is working fine and I am able to translate my app from en to fr and vice versa. The translations are working when refreshing the page too.
I have one React Component that is unable to translate and is showing a missing key error.
i18next::translator: missingKey fr translation CampaignDetails.description CampaignDetails.description
Instead of translating the text for description, it is showing me CampaignDetails.description exactly
PROBLEM EXPLANATION
Go to this URL http://donatenow-9cc92.web.app/
Try changing the language at the rightside of the header. It will work.
There is a search field at the leftside in the header. Search for "mervice" and wait until the
charity shows up
You can see a card for the searched charity, click on the card anywhere but not on follow
button
The URL now looks like this https://donatenow-9cc92.web.app/mervice
Try changing the language now and it will work too.
Refresh this page https://donatenow-9cc92.web.app/mervice and you will see the translations
are still working
Now scroll this page https://donatenow-9cc92.web.app/mervice and at the bottom, you will see
some campaigns like 'Sadqah', 'Zakat' and 'Dollar a Day for Sadaqa'. Click on any campaign,
lets say you have clicked 'Sadqah'
Now the URL is something like this https://donatenow-9cc92.web.app/mervice/sadqah
Try changing the language from this route and it will work fine
The problem is if you click on this link https://donatenow-9cc92.web.app/mervice/sadqah
directly or refresh the page. The translation will not work and the console will give you
missing keys error
EXPLANATION TO POINT 11
1- I have a NotFound.js component in which I am checking if the URL has any charity name like mervice. For example, if the URL is like this domainName/mervice then I am extracting the mervice from the URL and doing a Axios call to fetch the charity and then navigating to the charity page
https://donatenow-9cc92.web.app/mervice (The translations are working no matter if the user has come directly to the page or He/She searched for the charity)
2- In the same function of NotFound.js in which I am fetching the charity, I am also fetching the campaign for the charity. I am checking if the URL is like this domaninName/mervice/campaign and then if the campaign is found for the charity (mervice in this case) then I am navigating to the campaign page.
3- https://donatenow-9cc92.web.app/mervice/sadqah (The translation will not work if you click on this link directly but translations will work if you do it manually like using the search field)
If the translation is working for this page https://donatenow-9cc92.web.app/mervice then why it is not working for this page https://donatenow-9cc92.web.app/mervice/sadqah
The component is the same, I am only navigating in this component based on the condition
Index.js
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import "./css/Auth.css";
import "./css/SearchOrganization.css";
import "./css/Profile.css";
import "./css/Donations.css";
import "./css/ReceiptModal.css";
import "./css/Recurring.css";
import { Provider } from "react-redux";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import HttpApi from "i18next-http-backend";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
i18n
.use(initReactI18next)
.use(LanguageDetector)
.use(HttpApi)
.init({
debug: true,
supportedLngs: ["en", "fr"],
fallbackLng: "en",
detection: {
order: ["localStorage"],
caches: ["localStorage"],
},
backend: {
loadPath: "assets/locales/{{lng}}/translation.json",
},
interpolation: {
escapeValue: false,
},
});
root.render(
<Suspense fallback={null}>
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
</Suspense>
);
reportWebVitals();
NotFound.js
axios
.get(`${hostName}api/v1/search/organization?name=${lowercase}&type=shortName`)
.then((res) => {
if (res.data.data.length > 0) {
// setting states
setLoader(false);
setError("");
// local storage
localStorage.setItem("organization", JSON.stringify(res.data.data[0]));
// setting props
setOrganizationDetails(res.data.data[0]);
// setting organization path
const data = res.data.data[0];
const organizationName = data.shortName;
const cleanOrganizationName = organizationName.replace(
/[^a-zA-Z0-9]/g,
""
);
setOrganizationPath(cleanOrganizationName.toLowerCase()); // redirects to charity translation is working
// fetching campaign for the given organization
for (let i = 0; i < data.campaigns.length; i += 1) {
const word = data.campaigns[i].name.replace(/[^a-zA-Z0-9]/g, "");
const lowercase = word.toLowerCase();
if (secondString === lowercase) {
setCharityPath(lowercase); // redirects to campaign, translation not working
setCharityDetails(data.campaigns[i]);
localStorage.setItem("campaign", JSON.stringify(data.campaigns[i]));
break;
} else {
setCharityPath(null);
setCharityDetails(null);
localStorage.removeItem("campaign");
}
}
}
});

How can i customize form error message from Yup/Vee-validate using Vue-i18n in Ionic VueJS

I would like to use vue-i18n to translate my form error messages.
I've code a "lang.ts" file which builds my i18n instance like this :
import { createI18n } from 'vue-i18n';
import french from './i18n/fr-FR.json';
import english from './i18n/en-US.json';
// push translations to list
const messages = {
'en-US': french,
'fr-FR': english
}
// Create VueI18n instance with options
export const i18n = createI18n({
locale:"en-US",
messages
});
and I initialize all of this in my "main.ts"
...
const app = createApp(App).use(router);
router.isReady().then(() => {
Device.getLanguageCode().then(function (lang) {
i18n.global.locale = lang.value; // changing locale using Capacitor locale getter
app.use(i18n)
.use(IonicVue)
app.mount('#app');
});
});
I'm using a yup custom schema to validate my form, and that's where i mention my custom error message, the problem is that my "global" locale value isn't being used in my schema and it always translates to default language (en-US here...)
here's my schema
export const LoginFormContentSchema = yup
.object({
login: yup.object({
email: yup.string().email(i18n.global.t('wrongMail')).required(i18n.global.t('emptyMail')), // translating my error messages to current global locale
password: yup.string().required(i18n.global.t('emptyPassword')),
url: yup
.string()
.required(i18n.global.t("urlEmpty")).lowercase()
.matches(/^(https:\/\/)?[a-z0-9._-]+(\.fr)/),
}),
})
.required();
Since i've changed my locale before mounting the app, i should have fr-FR as locale everywhere, but i'm getting "en-US" outputs when the error message pops out even thought my device is in French and my global.locale is fr-FR.

Use same resource for languages with different IETF language tag in i18next

I have the following code:
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import locales from 'locales'
const resources = {
en: locales.en,
no: locales.no,
de: locales.de,
se: locales.se
}
function getDefaultLocale() {
const preferredLanguage = localStorage.getItem('preferredLanguage')
const lng = locales.languages
.map(({ code }) => code)
.find(code =>
code === preferredLanguage || // The user has set a preferred language
code === '__DEFAULT_LOCALE__' || // Default locale set at build time.
code === navigator.language
)
return lng
}
i18n
.use(initReactI18next)
.init({
resources,
lng: getDefaultLocale(),
appendNamespaceToCIMode: true
})
export default i18n
My problem is, that let's say when the English language is detected with navigator.language, it may be en, en-US, en-GB, or for Norwegian, it may be nb, nb-NO, ny-NO etc.
We would like to serve the same translations for all English language codes, and the same for all the Norwegian language codes.
My current solution is to defined resources like so:
const resources = {
en: locales.en,
'en-US': locales.en,
'en-GB': locales.en,
no: locales.no,
'nb-NO': locales.no,
'ny-NO': locales.no,
de: locales.de,
se: locales.se
}
But there must be a better solution to this! Could someone help?
Default behaviour for locales containing region is to fallback to the primary language code.
For instance, we have detected en-GB, but there is not translation file assigned to this locale. Assuming that en translations are in place, expected behaviour would be to proceed with them, not causing any trouble.
This practically means you can remove duplicate declaration from your initialization code:
const resources = {
en: locales.en,
no: locales.no,
de: locales.de,
se: locales.se
}
If you prefer, it could be done in a more concise manner:
const resources = { ...locales }

withTranslation HOC looks for translation in first namespace only

I'm upgrading my project from i18next^11.0.0 to i18next^15.0.0 and react-i18next^7.0.0 to react-i18next^10.0.0. I was using translate HOC previously but seems that it's replaced by withTranslation now. So my simple React component after these changes looks like:
import React from 'react';
import { withTranslation } from 'react-i18next';
const AboutControl = props => {
const { t } = props;
return (
<div className="about-control">
<p className="about-control-application-name">
{t('name')} {VERSION}
</p>
<p>
{t('supported-browsers')}:
</p>
<ul>
<li>Google Chrome >= 55</li>
<li>Mozilla Firefox >= 53</li>
</ul>
</div>
);
};
export default withTranslation(['about', 'application'])(AboutControl);
Translation for supported-browsers key is defined in about namespace while translation for name key is in application namespace. However new version of the library doesn't translate name key in the example above:
If I change an order of about and application in withTranslation call
export default withTranslation(['application', 'about'])(AboutControl);
everything becomes vice versa (supported-browsers is not translated):
In older version of react-i18next nsMode option was available that solved the issue but it doesn't work anymore:
await i18next.init({
whitelist: this.languages,
lng: this.language || this.languages[0],
fallbackLng: this.languages[0],
lowerCaseLng: true,
debug: false,
resources: {},
interpolation: {
escapeValue: false // not needed for React
},
react: {
wait: true,
nsMode: true
}
});
I didn't find anything related to this in documentation. Here is an example from there:
// load multiple namespaces
// the t function will be set to first namespace as default
withTranslation(['ns1', 'ns2', 'ns3'])(MyComponent);
Looks like that no any additional options are required otherwise I wonder what namespaces should be passed for if not to translate texts of a component. Is it a bug? Or does any workaround exist?
Migration guide from 9 to 10 doesn't highlight any changes on this behavior.
I had the same problem described (the ns specified in withTranslation function was omitted) and after looking for a solution and not finding anything, I tried something different and it was the right solution.
Instead of prefix keys by namespace, you can configure the namespace to use using:
const {t} = useTranslation ('yourNSname');
You can continue using the prefix keys to access values ​​from other namespaces.
I hope it helps you or someone with the same problem.
react-i18next didn't have nsMode since version 10.0.0. But this pull request adds it back (published in 10.7.0).
One could translate texts (even without passing namespaces to withTranslation or useTranslation) just by prefixing keys by namespace (ns:key):
import React from 'react';
import { withTranslation } from 'react-i18next';
const AboutControl = props => {
const { t } = props;
return (
<div className="about-control">
<p className="about-control-application-name">
{t('application:name')} {VERSION}
</p>
<p>
{t('about:supported-browsers')}:
</p>
<ul>
<li>Google Chrome >= 55</li>
<li>Mozilla Firefox >= 53</li>
</ul>
</div>
);
};
export default withTranslation()(AboutControl);
Passing namespaces to withTranslation is required when you load translation resources asynchronously to ensure that they are available before rendering.

Vue warn: 'Unknown custom element' with re-exported component

I have an index.js file in my Vue project's components folder which allows me to import components like this:
import { home, search, tour } from '#/components';
The index.js file:
export { default as home } from './home/home.vue';
export { default as search } from './search/search.vue';
export { default as tour } from './tour/tour.vue';
export { default as tourItem } from './tour-item/tour-item.vue';
Now, when doing this with nested components (a component that should be used in another) it gives me the unknown custom element error.
I don't get why that error is thrown - it's just another component, right?
To be more clear, this works:
import tourItem from '#/components/tour-item/tour-item.vue';
And this doesn't:
import { tourItem } from '#/components';
I had the same issue. Try export children/local component just BEFORE parent in index.js. This solve problem in my case.
Solution in Vue forum