i18next with namespace does not fetch expected translation file - javascript

I'm trying to setup localisation for an existing app. I've gotten fairly far. But something is not quite right.
My setup is having multiple translation calls in the files like so: {attribute: 'stress', text: t('dass21::I found it hard to wind down')},
As you can see, I changed the namespace separator to be a double :: since I have a lot of medical specific sentences which are hard to think keys for, so the actual sentence is the key.
To export the translation into json translations files I use: i18next 'src/**/*.js' which works fine. It makes a folder for every language, and in that folder, for every namespace it makes a file. (Eg in this case, it will make a file dass21.json).
My configuration for the i18next-parser is:
module.exports = {
// Since keys hold regular english I can't have the default settings
namespaceSeparator: '::',
keySeparator: '_',
pluralSeparator: "|",
locales: ['en', 'es'],
output: 'assets/locales/$LOCALE/$NAMESPACE.json',
}
I matches those settings in the i18next init like so:
import i18next from "i18next";
import HttpApi from 'i18next-http-backend';
i18next
.use(HttpApi)
.init({
backend: {
// for all available options read the backend's repository readme file
loadPath: '/assessments/locales/{{lng}}/{{ns}}.json',
},
fallbackLng: 'en',
lng: 'en',
supportedLngs: ['en', 'es'],
namespaceSeparator: '::',
keySeparator: '_',
pluralSeparator: "|",
});
const t = i18next.t
export default t
As you can see, I want to load my translation files over http (which are served by a node server and this works). However, it only tries to load a generic translation file called translation.js and none of my specific namespace files. Even though the call to t with dass21::I found it hard to wind down is called. (Since it is visible on the screen using its key).
How can I make sure it also tries to load the namespace specific files over http, while having the custom namespace separators?

If you're using i18next, make sure you pass in the desired namespace in to the useTranslation or withTranslation function: https://react.i18next.com/guides/multiple-translation-files
Alternatively, use the ns init option and define your namespaces.

Related

Using i18next for a react production build causes the translation to display only strings

I believe I have searched all parts of the internet for an answer and fixes but none seem to work.
Issue :
When building a react app for production that uses i18n as a translation will cause the production build to only display the strings only as a result and not the translated text itself (see picture reference)
The weird part is that inside localhost when the development server is running it display the translation correctly. (see picture for reference)
After building the production build, inside the browser console (using chrome) it displays the following error : Fetch API cannot load file:///Documents/streaming_site/build/static/locales/en/translation.json. URL scheme must be "http" or "https" for CORS request.
After seeing this error, I imediately came to the assumption that it was inside my i18next.js file that was causing the issue. Here is the file :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import XHR from 'i18next-xhr-backend';
// don't want to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const Languages = ['en', 'fr'];
i18n
.use(XHR)
// load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
// learn more: https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
lng : 'en',
react: {
useSuspense: false,
wait: true
},
fallbackLng: 'en',
debug: false,
whitelist: Languages,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
nsSeperator : false,
keySeperator : false,
backend:{
loadPath: () => {
var cors = require('cors');
var app = cors();
// check the domain
const host = window.location.host;
return (host === 'index.html#' ? '':'') + '/static/locales/{{lng}}/{{ns}}.json';
},
}
});
export default i18n;
Furhtermore inside my index.js file I added these extra fixes to ensure that the app would properly display the translated text on a production build :
i18next.init({// <-- This was added
interpolation: {escapeValue: false},
});
i18next.init().then(() => // <-- This was added
ReactDOM.render(
<React.StrictMode>
<Suspense fallback={<div>Loading...</div>}>// <-- This was added
<I18nextProvider i18n={i18next}>// <-- This was added
<App />
</I18nextProvider>
</Suspense>
</React.StrictMode>,
document.getElementById('root')
)
);
Alas, the same error occurs (the one from the third point) after making an npm run build
This leads me to believe that it is impossible without a backend server hosting the translation.json files for en and fr to have them be accessed locally.
My questions is the following: Is it possible to have i18n translation run locally after building the production build without needing a server to host the json files? If yes how would I proceed?
For references I have tried a few fixes from this website such as :
React i18next Backend-Path different in local and production environment
react-i18next doesn't translate
Allow Access-Control-Allow-Origin header using HTML5 fetch API
I've gotten close when I attempted --> res.header('Access-Control-Allow-Origin', "*"); although it comes at the cost of posing some security issue and I don't want that. Although if this is a potential fix, I am willing to try it.
So here I am out of ideas... :/
Looks like there is some misconfig of the i18next service.
First of all, i18next-http-backend & i18next-xhr-backend are solving the same problem, fetching json files.
Remove the second one (i18next-xhr-backend) since it is deprecated in favor of i18next-http-backend.
Change the backend.loadPath to be a string, your code doesn't make sense.
it should look like:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
// don't want to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const Languages = ['en', 'fr'];
i18n
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
lng: 'en',
react: {
useSuspense: false,
wait: true,
},
fallbackLng: 'en',
debug: false,
whitelist: Languages,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
nsSeperator: false,
keySeperator: false,
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
export default i18n;

How to get locale for localeCompare from i18next?

Our project is using i18next for translations, and I'm currently needing to sort strings using String.prototype.localeCompare
How would I go about getting a code like 'en', 'fr', 'en-US', etc from i18next? Does i18next default to a specific code? Would I need to use an i18next instance or the default export?
I believe these might be called "BCP 47 language tags"
Thanks
Ok, so the brief answer is that you just use i18next.language
In our case, it did matter that we used the correct instance, not just the default import. We also used an additional layer called react-i18next as shown below.
import { I18nContext } from "react-i18next";
...
const { i18n } = useContext(I18nContext);
...
return isLocaleCompareSupported()
? aText.localeCompare(bText, i18n.language)
: aText.localeCompare(bText);

Missing key in i18next translation

I am using http://i18next.com/ for multi-language support in my Javascript app.
However, I am running into an issue where, presumably, the Javascript that renders HTML is loaded before the file i18next uses to find translations, and instead of the translation text, I get an error.
For example, for a key-translation pair of "popups.notifications":"Account Notifications" I'll get:
i18next::translator: missingKey undefined translation popups.notifications popups.notifications
The i18next JS is included in the page header before any other Javascript, but it makes no difference.
How do I make sure the translation file is loaded before any HTML rendering takes place?
I had the same problem with using i18n-fetch-backend with the following config:
.init({
...
backend: {
loadPath: "somePatch",
parse: (data) => {console.log(data)}
}
})
Removing the parse attribute solved it for me.

meteor: import directory - modular import of methods which are needed

I'm migrating my meteor application to the import-function of meteor 1.3.
But I think this is not quite the best way it should be done. Isn't it possible to load/import just the method which is really needed?
I mean, right now just all methods are loaded by importing the the methods.js. But I would like to do that in a modular way. So if the form .fomNewElement is used in the app, the method insertArticle will be imported and so on. Not just loading everything...
Below you can see my folder structure for /imports and some content of the files. Is there anything more I could improve in the structure itself?
Also it would be great if the import would depend on user roles. Is this possible?
imports/api/article/client/article.js
import { Articles } from '../';
import { insertArticle, updateArticle } from '../methods.js';
Template.Articles.helpers({
// some helpers
});
Template.Artilces.onCreated(function() {
// some code
});
Template.Artilces.onRendered(function() {
// some code
});
Template.Articles.events({
'submit .formNewElement': function(event) {
event.preventDefault();
var title = event.target.title.value.trim();
insertArticle.call({
title: title
});
},
'click .anything': function() {}
});
As you can see, I put into that js-file all helpers, events and onCreated/onRendered code. Hope this is 'correct'... Please give me some hint, if this isn't very smart.
imports/api/article/index.js
export const Articles = new Mongo.Collection('articles');
imports/api/article/methods.js
import { Articles } from './';
export const insertArticle = new ValidatedMethod({
name: 'article.insert',
validate: new SimpleSchema({
title: { type: String }
}).validator(),
run( document ) {
Articles.insert( document );
}
});
export const updateArticle = new ValidatedMethod({
name: 'article.update',
validate: new SimpleSchema({
_id: { type: String },
'update.title': { type: String }
}).validator(),
run( { _id, update } ) {
Articles.update( _id, { $set: update } );
}
});
And the other files:
imports/startup/client/index.js
import '../../api/redactor-article/client';
imports/startup/server/index.js
import '../../api/redactor-article/server/publications.js';
import '../../api/redactor-article/methods.js';
imports/api/article/client/index.js
import './article.html';
import './article.sass';
import './article.js';
Filestructure
/imports
/api
/article
/client
article.html
article.js
article.sass
index.js
/server
publications.js
index.js
methods.js
Update
Maybe it would be a better way to structure an import module like this:
imports/
api/
articles/
publication.js
methods.js
collection.js
ui/
articles/
article.html
article.css
article.js // contains helpers, events and onCreated/onRendered
Then I have to import the files in startup/client (-> all ui files of this module AND all api files) and startup/server (-> just all api files)...
Right?
A few points:
You've put everything under imports/api. That directory is designed for collections, methods, helpers, 'business logic' and public API (e.g. if you expose a REST API, you'd do it from within that directory). Use imports/ui for your templates (including their styles and associated .js files).
You don't need to differentiate between client and server directories within imports. Just import the files you need from the respective main entry points (i.e. client/main.js and server/main.js). This point is a little more complex than I suggest here, see the link to 'structure' in the Meteor Guide, below.
index.js doesn't seem like a logical place to put your Articles collection. I'd make a file at /imports/api/articles/articles.js for it. See http://guide.meteor.com/structure.html for a good overview about where to put things and why.
Also, in the interests of following best-practices, use a default export for your Articles collection: http://guide.meteor.com/code-style.html#collections
To answer your question about how much of the file is exported (i.e. which functions), there's not much you can do about everything being loaded. The bundler needs to read the entire JS file anyway (imagine you exported an object and then changed it further down in the same file– not the best practice, but possible). If you're not using a function though, by all means, don't import it! And you can always split up your methods into seperate files if they get unmanageable.
Regarding your question about only importing bits for certain user roles: always avoid using imports or other types of obfuscation for security. The ideal way to do security on Meteor is to assume ANYTHING is accessible on the client (it pretty much is) and code your server-side code accordingly. That means, if you have an admin area, assume that anyone can access it. You can do checks in server methods and publications for this.userId and do a database lookup there to ensure the user has the correct privileges. Again, the guide has more info about this: http://guide.meteor.com/security.html
A final note about imports/exports: the idea behind them is not to reduce code size, but to provide a graph of what is actually being used (and leaving out the files that aren't) to make hot code reloading faster for a better development experience. They also make for cleaner application code that is easier to understand, because you don't have random magical globals swimming around that could have come from anywhere, and help to keep logically distinct pieces of code separate.
Best of luck :)

Setting RequireJS i18n locale dynamically

I am using the RequireJS i18n plugin to load translations into my application. I'm struggling with the concept of runtime determination of a user's preferred language.
The plugin works well if you're using navigator.language to determine the user's preferred language, but in my app, the user's language is held in a database on the server. So I need to set the locale at runtime:
require.config({
config: {
i18n: {
locale: userLocale
}
}
});
So what I need is a clever way of setting userLocale before RequireJS has loaded my application. Does anyone know what would be the best way to achieve this? Possibilities include:
1) Setting userLocale outside of my application, in a non-AMD way:
//run Ajax call to determine user's localization preferencess
var Localization = Localization || getUserLocalization();
//and then...
require.config({
config: {
i18n: {
locale: Localization.userLocale
}
}
});
require(['app']);
This makes me a little bit sad as it means some of my application will be outside of RequireJS, and thus untidy. It also means all the user's localization settings (language timezone, date format, numeric format) will be held in the global namespace.
2) Having a separate require call to retrieve localization settings
I'm not sure how this work, but perhaps:
var Localization = require(['localization']);
require.config({
config: {
i18n: {
locale: Localization.userLocale
}
}
});
require(['app']);
Perhaps this wouldn't work due to asynchronousness? Also the app would not have access to the Localization object, so it would still need to be stored as a global variable.
Can anyone see a good solution to this problem? Has anyone used the RequireJS i18n plugin to do something similar?
It seems after a lot of research, the best approach for solving this problem is to check localStorage for a locale value. If this hasn't been set yet, I load the application using a dummy language:
var locale = localStorage.getItem('locale') || 'dummy';
require.config({
config: {
i18n: {
locale: locale
}
}
});
require(['app']);
I use a language called dummy, set to an empty object in my nls file. Using a dummy, rather than a default, means I don't have to guess what the user's language might be, and potentially force them to download a whole load of translations in the wrong language:
define({
"root": false,
"dummy": {}, //dummy language with no translations if user language is unknown
"fr": true,
"en": true,
"en-uk": true,
"fr-fr": true
});
Then, when the app has loaded and the user has been logged in, I query the database using a service call, set the language in localStorage and reload the app using location.reload():
//retrieve user object (including preferred locale) from service call
user = getUserObject(userId);
locale = localStorage.getItem('locale');
if (!locale || locale !== user.locale) {
localStorage.setItem('locale', user.locale);
//reload the app
location.reload();
}
Of course, I need to support old version of IE so I have also included fallbacks using userData, but this is the gist of the solution.
This approach is partially taken from how the guys at RESThub do it.
Another option, if your page is dynamically generated with a template system, is to have the require.config({config {..} }) inlined into the generated HTML... say like this:
<!-- load require.js -->
<script src="js/lib/require.js"></script>
<!-- standard config options in this file -->
<script src="js/config.js"></script>
<!-- user specific config inlined in the Dynamic HTML -->
<script>
// now set the user's preferred locale
require.config({
config : {
i18n: {
locale: '<% user.locale %>' // i.e. use PHP to insert user's preferred language
}
}
});
require(['app']); // Call your main app here
</script>
require.config(..) can be called multiple times, but should be done before your app is loaded.

Categories

Resources