react-i18next, add resources from props - javascript

I have repo with components and repo with main app. I implemented i18next in repo with components and it works fine when I have an i18n config file in this repo (or when I pass it by props from app repo). But I have a problem when I'm trying to send only "resource" part from main app and replace it in config file in components. I tried to clone i18n instance and set resources but, it's not work.
It's my config file:
i18n.js
import i18n from 'i18next';
import LngDetector from 'i18next-browser-languagedetector';
import { reactI18nextModule } from 'react-i18next';
i18n
.use(LngDetector)
.use(reactI18nextModule)
.init({
detection: {
order: ['cookie', 'localStorage'],
lookupLocalStorage: 'i18n_lang',
lookupCookie: 'i18n_lang',
caches: ['localStorage'],
},
load: 'current',
fallbackLng: 'en',
ns: ['components'],
defaultNS: 'components',
keySeparator: false,
interpolation: {
escapeValue: false,
formatSeparator: ',',
},
react: {
wait: true,
},
});
export default i18n;
resources.js file (I tried with resources key at beginning but it's still doesn't work):
import * as en from './en.json';
import * as de from './de.json';
export default {
en: {
components: en,
},
de: {
components: de,
},
};
Now I tried something like this:
import * as langs from './resources';
const newI18 = i18n.cloneInstance({ resources: langs });
const i18ProviderDecorator = (storyFn) => (
<I18nextProvider i18n={newI18}>
{ storyFn() }
</I18nextProvider>
When I pass i18n.js by props with resources, it works perfect, but I want to remove i18next from main app and leave it only in the components.
Greetings

a i18next cloned instance uses the same store as the original instance -> and does not init that again -> so passing in resources that way does not work: https://github.com/i18next/i18next/blob/master/src/i18next.js#L308
make a new instance i18n.createInstance or pass resources to clone using i18n.addResourceBundle: https://www.i18next.com/api.html#addresourcebundle

Related

React.js i18n dynamically change language

I am using "i18n" for multilingual support in my "React.js" project. I have a "Switch" button in "Navbar" component. and when that button is clicked I add current language information to "lcoalstore". And when I check in browser, language option works dynamically in "localstore". My problem is I want to change app language every time user changes language. But that doesn't happen.
Here is my i18n code:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import messages_az from "./translations/az.json";
import messages_en from "./translations/en.json";
// the translations
// (tip move them in a JSON file and import them)
const resources = {
en: {
translation: messages_en,
},
az: {
translation: messages_az,
},
};
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: localStorage.getItem("lang"),
detection: {
order: ["localStorage", "lang"],
lookupQuerystring: "lang",
lookupLocalStorage: "lang",
caches: ["localStorage"],
},
keySeparator: false, // we do not use keys in form messages.welcome
fallbackLng: "az", // use az if detected lng is not available
interpolation: {
escapeValue: false, // react already safes from xss
},
});
export default i18n;
I fixed That!
Here is my swtich button :
<Menu.Item key="lang" className=" navMenuItem">
<Switch
onChange={langSwitch}
className="langSwitch"
checkedChildren="En"
unCheckedChildren="Az"
defaultChecked
/>
</Menu.Item>
So, i Added this code to my langSwitch() function :
function langSwitch(checked) {
console.log(`switch to ${checked}`);
if (checked) {
i18n.changeLanguage("en");
} else {
i18n.changeLanguage("az");
}
}

characters wont display correct with Quasar Framework together with i18n

Im using i18n together with Quasar Framework. It works great but I have some problems with some special characters in certain languages when grabbing text from my own definations.
I got this in i18n.js in my boot:
import Vue from "vue";
import VueI18n from "vue-i18n";
import messages from "src/i18n";
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: "en-us",
fallbackLocale: "en-us",
messages,
});
export default ({ app }) => {
// Set i18n instance on app
app.i18n = i18n;
};
// if you need to import it from
// other files, then:
export { i18n };
and use it in template like this:
<template>
...
<div>{{ $t('hello') }}</div>
...
and in my bootfile
boot: ["i18n"],
framework: {
iconSet: "material-icons", // Quasar icon set
lang: "en-us", // Quasar language pack
Here is the setup in my language component:
export default {
data() {
return {
toggleSMenu: false,
myData: ["one", "two"],
lang: this.$i18n.locale,
langOptions: [
{ value: "en-us", label: "EN" },
{ value: "nl", label: "NL" }
]
};
},
When I change to Dutch (NL) some caracters wont display correctly. How can I add support for dutch letters?

Vue i18n translation for single file components

I'm using laravel and currently trying to do multilanguage pages,
So i've found this pretty neat plugin called VueI18N for translations and got it working (somehow) by installing it via npm and then putting the following code in my app.js
//app.js
window.Vue = require('vue');
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
//tons of more components here
Vue.component('vue-test', require('./components/VueTestFileForLocalization.vue').default);
const messages = {
en: {
message: {
hello: 'Hello, {name}!'
}
},
de: {
message: {
hello: 'Guten Tag, {name}!'
}
}
};
const i18n = new VueI18n({
locale: 'de',
messages
});
const app = new Vue({
el: '#vue-app',
i18n
});
Then in my vue-test i tried outputting this successfully:
<template>
<div>{{ $t('message.hello', { name: 'John' }) }}</div>
</template>
<script>
export default {
data() {
return {};
},
created() {
this.$i18n.locale = 'en';
}
};
</script>
and by changing the locale i can also display the other language. Great.
Now I think with so many components, I might have a problem defining all the localization inside app.js , and its not beautiful either. So I tried looking up This link here to the docs for single file components but unsuccessfully, unfortunately.
I copy-pasted the code, (vue-i18n-loader should also be installed by laravel by default) and modified the webpack file. The error I get seems pretty common after research but I cannot seem to fix it.
Value of key 'hello' is not a string!
Cannot translate the value of keypath 'hello'. Use the value of keypath as default
It does simply output whatever the key is i specify in message.
Does any of you out there have an idea, what I might have done wrong or forgot to setup?
Any hints would be appreciated very very much.
Thank you for your time
Best regards,
Desory
While not a direct answer to your question I recently found another approach to the same problem that is less effort when it comes to maintaining translations.
I put all my translations in JSON files so I can share the same translations between Laravel backend and Vue front end.
I did this based on this:
https://www.codeandweb.com/babeledit/tutorials/how-to-translate-your-vue-app-with-vue-i18n
So as per: https://laravel.com/docs/7.x/localization#using-translation-strings-as-keys
Create resources/lang/en.json etc. with contents:
{
"my_message": "This is my message in english",
...
}
I'd create resources/js/i18n.js containing:
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
function loadLocaleMessages() {
const locales = require.context(
"../lang",
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
const messages = {};
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key);
}
});
return messages;
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || "en",
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en",
messages: loadLocaleMessages()
});
and in app.js import that as follows:
//Localise
import i18n from "./i18n";
Vue.use(i18n);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
i18n,
el: "#app"
});
You can then use the same translations in your blade templates with the __ helper and in Vue with $t(...)
Try the changes below for app.js and your code should work fine:
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import App from './components/VueTestFileForLocalization.vue';
Vue.use(VueI18n);
const messages = {
en: {
message: {
hello: 'Hello, {name}!'
}
},
de: {
message: {
hello: 'Guten Tag, {name}!'
}
}
};
const i18n = new VueI18n({
locale: 'de',
messages
});
new Vue({
i18n,
render: h => h(App)
}).$mount('#vue-app');
I had the same problem, i solved it by restarting the server.
Run npm run serve again.
Hope it helps someone in the future..

How to use Webpack to load a static file with a relative path with React?

I'm trying to create a map component in React using the Tangram Library.
I got it working with Webpack alone but it started bugging out when I used react in the mix.
I've tried using various loaders such as a raw loader, a yaml loader and so forth, but none of them have worked thus far.
The map component looks as follows:
// -- Import dependencies --
import React from 'react';
import { Map } from 'react-leaflet';
// -- Import Tangram --
import Tangram from 'tangram';
import yaml from 'js-yaml';
import data from '!!raw-loader!./scene.yaml';
export default class LeafletMap extends React.Component {
componentDidMount() {
const layer = Tangram.leafletLayer({
scene: data
});
layer.addTo(this.map.leafletElement);
}
render() {
return (
<Map center={[40.70532, -74.00976]} zoom={15} ref={(ref) => { this.map = ref }} />
);
}
}
How can I actually load the scene.yaml so that the Tangram library makes use of it ?
In the end it responds with a 404 as the file isn't found.
The solution was, that the static files weren't being copied to the bundle built by webpack.
I solved it by using the CopyPlugin in the webpack config and copying the files to a folder respecting the relative path name, like so:
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: './main.js',
output: {
filename: './bundle.js'
},
plugins: [
new CopyPlugin([
{ from: 'src', to: 'src' },
]),
],
};

How do I unit test a Vue.js component that relies on a complicated Vuex store and extends another component?

I am wondering how one goes about unit testing a component that uses a complicated Vuex store and extends another component.
Can someone please provide me with an example of how I might go about creating a test that simply asserts that a component that extends another component and relies on Vuex mounts and displays some simple text?
I've tried using vue-test-utils to shallowMount the component under test, but I can't get my test to fail because it has issues even building and mounting the component. As far as I can tell, this is a result of the component leveraging the extended component, and because both components rely on a complicated Vuex store.
Any kind of examples would be appreciated. Thanks.
EDIT:
For further context, our store is broken up into modules. Here is what the store definition file looks like:
/* global phpvars */
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import * as mutations from './mutations'
import acloverrides from '../../modules/acloverrides'
import api from '../../modules/api'
import callback from '../../modules/callback'
import clearlink from '../../modules/clearlink'
import configuration from '../../modules/configuration'
import customer from '../../modules/customer'
import drKeepAlive from '../../modules/drkeepalive'
import interaction from './modules/interaction'
import ivr from './modules/ivr'
import marketing from '../../modules/marketing'
import opportunities from './modules/opportunities'
import order from '../../modules/order'
import orderNotes from './modules/notes'
import products from '../../modules/products'
import sidebar from './modules/sidebar'
import sparks from './modules/sparks'
import training from '../../modules/training'
import transformers from '../../modules/transformers'
import user from '../../modules/user'
let brand = require('../brands/' + phpvars.brand.name + '/modules/brand').default
let forms = require('../brands/' + phpvars.brand.name + '/modules/forms').default
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
acloverrides,
api,
brand,
callback,
clearlink,
configuration,
customer,
drKeepAlive,
forms,
interaction,
ivr,
marketing,
opportunities,
order,
orderNotes,
products,
sidebar,
sparks,
training,
transformers,
user
},
state: {
availability: {
status: false,
results: {}
},
navigation: {
enabled: phpvars.user.access.order.navigate,
restrictTo: []
},
routes: [],
router: {},
editMode: false // Used to determine if the user has clicked the edit button on an existing order.
},
actions,
getters,
mutations
})
And here is my unit test file:
import Vuex from 'vuex'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import SelectedProducts from '../../resources/assets/js/components/formfields/products/SelectedProducts'
import BaseField from '../../resources/assets/js/components/BaseField'
import store from '../../resources/assets/js/orderform/store/index.js'
const Vue = createLocalVue()
Vue.use(Vuex)
describe('SelectedProducts', () => {
fit('sanity check', () => {
window.phpvars = {
brand: {
name: 'foobar'
},
user: {
access: {
order: {
navigate: true
}
}
}
}
const wrapper = shallowMount(SelectedProducts, {
store: new Vuex.Store(store)
})
expect(wrapper.text()).toContain('Selected Products')
})
})
I find Vue documentation on unit testing to be a bit vague. Give this a shot:
import {createLocalVue, shallowMount} from '#vue/test-utils';
import Vuex from 'vuex';
import store from 'path/to/store/index.js';
import Component from 'path/to/Component';
// create a local instance that uses
// the store, should follow a pattern present
// in your src/main.js
const localVue = createLocalVue();
localVue.use(Vuex);
describe(
'Component', () => {
test('renders', () => {
const wrapper = shallowMount(Component, {
store: new Vuex.Store(store)
});
expect(wrapper.isVueInstance()).toStrictEqual(true);
});
}
);
EDIT for your edit
In Jest, window is replaced by global. So you could mock your phpvars with:
global.phpvars = {
brand: {
name: 'foobar'
},
user: {
access: {
order: {
navigate: true
}
}
}
};
You'll want to place that before you import your store.
Components that extend other components shouldn't be tested any differently, they essentially compile down to a single component in terms of variables and requirements. If you could expand on that question, I'd be happy to answer (like, what issues or errors you are encountering unique to your extended components).
I haven't tested anything I've mentioned so if you do continue to have errors I'll throw together a project. Good luck!

Categories

Resources