Problem loading localized messages for vue I18n - javascript

when I initialize vue-I18n plugin I can't load localized messages for some reason.
I'm using context.require to get all json objects inside folder but it's not working for some reason? Is it because I also have folders with the same name as the json files: en.json,es.json along with es and en folders laravel uses for backend translations.
My folder structure
https://i.imgur.com/BKkZYXC.png
And I18n.js file where I initialize the plugin is in resources
This is how I load messages:
function loadLocaleMessages() {
const locales = require.context(
"../lang",
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
console.log('locales',locales);
const messages = {};
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
console.log('locale',locale);
messages[locale] = locales(key);
}
});
return messages;
}
I18n.js file
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
//Antes habia un locales en resources
function loadLocaleMessages() {
const locales = require.context(
"../lang",
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
console.log('locales',locales);
const messages = {};
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
console.log('locale',locale);
messages[locale] = locales(key);
}
});
return messages;
}
function getStartingLocale() {
if (localStorage.getItem('last-locale')) {
return localStorage.getItem('last-locale')
}
return "es";
}
export default new VueI18n({
locale: getStartingLocale(),
fallbackLocale: "es",
messages: loadLocaleMessages()
});

Related

How to import multiple locale json files in Vue 3 + i18n?

This is my code, that works without problems:
import { createI18n } from 'vue-i18n'
import messages from './components/json/foo/foo_messages.json'
const app = createApp(App)
installI18n(app)
const i18n = createI18n({
locale: 'ru',
messages
})
app
.use(i18n)
.use(vuetify)
.mount('#app')
Now I need to load messages also from ./components/json/bar/bar_messages.json. I tried to do this way:
import { createI18n } from 'vue-i18n'
import foo_msg from './components/json/foo/foo_messages.json'
import bar_msg from './components/json/bar/bar_messages.json'
const app = createApp(App)
installI18n(app)
const i18n = createI18n({
locale: 'ru',
messages: {foo_msg, bar_msg}
})
app
.use(i18n)
.use(vuetify)
.mount('#app')
But it didn't work. Could anyone say how to do it?
EDIT: This is my foo json file
{
"ru": {
"header": {
"hello": "Привет"
}
},
"en": {
"header": {
"hello": "Hello"
}
}
}
and this is bar json file
{
"ru": {
"footer": {
"bye": "Пока"
}
},
"en": {
"footer": {
"bye": "Goodbye"
}
}
}
What you are trying to do is not very scalable. Given the format of the i18n JSON messages, you need to merge the input files to something like this:
{
"ru": {
"header": {
"hello": "Привет"
},
"footer": {
"bye": "Пока"
}
},
"en": {
"header": {
"hello": "Hello"
},
"footer": {
"bye": "Goodbye"
}
}
}
...this is definitely possible with JS but you must still import the JSON file for each component in your main.js which is tedious and error prone
Did you consider using vue-i18n custom blocks in your components? You can even keep the translations in external JSON files and use a custom block like <i18n src="./myLang.json"></i18n>
this is much better approach but if you stil want to use yours, here is a simple code how to merge all translation files (objects imported from JSON) into a single object usable by vue-i18n:
// import foo_msg from './components/json/foo/foo_messages.json'
const foo_msg = {
"ru": {
"header": {
"hello": "Привет"
}
},
"en": {
"header": {
"hello": "Hello"
}
}
}
// import bar_msg from './components/json/bar/bar_messages.json'
const bar_msg = {
"ru": {
"footer": {
"bye": "Пока"
}
},
"en": {
"footer": {
"bye": "Goodbye"
}
}
}
const sources = [foo_msg, bar_msg]
const messages = sources.reduce((acc, source) => {
for(key in source) {
acc[key] = { ...(acc[key] || {}), ...source[key] }
}
return acc
},{})
console.log(messages)
The accepted solution is already a good solution, but if you assist to use .json files to translate text. Here is my solution.
Use vue-cli to add i18n dependency, it would generate all the requirement files that we need.
vue add vue-i18n
It would generate the locales folder inside src, which it stores all the translation json files.
Then it would generate couple env variable on .env file and a i18n.js file
here is the i18n.js it generates
import { createI18n } from 'vue-i18n'
/**
* Load locale messages
*
* The loaded `JSON` locale messages is pre-compiled by `#intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`.
* See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation
*/
function loadLocaleMessages() {
const locales = require.context('./locales', 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).default
}
})
return messages
}
export default createI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
In our main.js, i had seen that vue has already add the component for me
import i18n from './i18n'
const app = createApp(App).use(i18n)
*Edit
I am using vite for building vue project, the loadLocaleMessages does not work in my case.
I made some modification. It needs to manually import all the json files, but i did not find any alternative solution.
I also change the env variable with 'VITE' prefix, and process.env to import.meta.env.
// import all the json files
import en from './locales/en.json'
import zh from './locales/zh.json'
/**
* Load locale messages
*
* The loaded `JSON` locale messages is pre-compiled by `#intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`.
* See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation
*/
function loadLocaleMessages() {
const locales = [{ en: en }, { zh: zh }]
const messages = {}
locales.forEach(lang => {
const key = Object.keys(lang)
messages[key] = lang[key]
})
return messages
}
export default createI18n({
locale: import.meta.env.VITE_APP_I18N_LOCALE || 'en',
fallbackLocale: import.meta.env.VITE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})

Change the URL path using I18next-react

Hello there, I can't change the href path (URL) after selecting a new language
import i18n from 'i18next';
import { useTranslation, initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import I18NextHttpBackend from 'i18next-http-backend';
i18n.on('languageChanged', function (lng) {
if (lng === i18n.options.fallbackLng[0]) {
if (window.location.pathname.includes('/' + i18n.options.fallbackLng[0])) {
const newUrl = window.location.pathname.replace(
'/' + i18n.options.fallbackLng[0]
);
window.location.replace(newUrl);
}
}
});
i18n
.use(I18NextHttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: ['en'],
whitelist: ['en', 'de', 'it', 'es'],
detection: {
order: ['path', 'cookie', 'htmlTag', 'localStorage', 'subdomain'],
caches: ['cookie'],
lookupFromPathIndex: 0,
checkWhitelist: true,
},
backend: {
loadPath: '/localization/{{lng}}/translation.json',
},
interpolation: {
escapeValue: false,
},
});
export default i18n;
I got example.com/undefined/page1
I used this way to import the language path to the Href
export const baseUrl = i18n.language === 'en' ? '' : '/' + i18n.language;
and the link <a>home</a>
<a className='item' href={baseUrl + '/'} > Home </a>
We have created a LanguageChanger component. A handleClick function determines the url and updates as you have stated but on LanguageChanged does not provide the same flexibility imo.
I have included two versions of newUrl. First, en is not present in the url. Second, if languageCode is present in the url.
/**
* Handles the language change
* #param code - updated language
* #global lang - current set language in state
*/
let handleChange = (code) => {
if (lang !== code) {
// v1 - below lines since 'en' is not present in the url as your baseUrl definition
let newUrl;
if (lang === "en") {
newUrl = window.location.pathname.replace(`/`, `/${code}/`);
} else if (code === "en") {
newUrl = window.location.pathname.replace(`/${lang}`, ``);
} else {
newUrl = window.location.pathname.replace(`/${lang}`, `/${code}`);
}
// v2 - below is the version if language is always present in the url
// let newUrl = window.location.pathname.startsWith(`/${lang}`) ? window.location.pathname.replace(`/${lang}`, `/${code}`) : window.location.pathname.replace(`/`, `/${code}/`)
window.location.replace(newUrl);
localStorage.setItem("i18nextLng", code); // this writes to the localStorage to keep the change
setLang(code); // this is coming from useState
i18n.changeLanguage(code);
}
};

How to render html with pug at runtime in a project served by gulp?

I am new to web development, and am using gulp for my project, which serves index.html page at build time.
I am using pug as a template for generating html.
gulfile.js
const bundler = () => {
return rollup({
input: './src/scripts/main.js',
plugins: [
babel(pkg.babel),
nodeResolve(),
commonJS(),
],
}).then((bundle) => bundle.write({
file: '.tmp/bundle.js',
format: 'umd',
sourceMap: 'inline',
}));
};
// Uses PUG as template
const templates = (env) => () => {
return src('./src/templates/*.pug')
.pipe(plugins.pug({locals: {
title: pkg.title,
description: pkg.description,
env,
}}))
.pipe(dest('dist'))
.pipe(plugins.size({title: 'templates'}));
};
...
exports.serve = series(
bundler,
styles,
templates('development'),
images('development'),
serve
);
Now this is my
main.js:
import dispatchForm from './modules/dispatchForm';
const domContentLoad = (fn) => {
if (document.readyState !== 'loading') fn();
else document.addEventListener('DOMContentLoaded', fn);
};
domContentLoad(() => {
dispatchForm();
});
And this is my module which exports the function to handle user interaction at index.html:
dispatchForm.js
const button = document.querySelector('[data-calculator-button]');
function updateValue() {
const gain = document.querySelector('[data-calculator-form][name="gain"]:checked');
const cost = document.querySelector('[data-calculator-form][name="cost"]:checked');
if (gain && cost) {
button.removeAttribute('disabled');
button.classList.remove('selectors__button--inactive');
} else {
button.setAttribute('disabled', '');
button.classList.add('selectors__button--inactive');
}
console.log(gain, cost)
}
function dispatchForm() {
const radioInput = document.querySelectorAll('[data-calculator-form]');
radioInput.forEach(element => element.addEventListener('input', updateValue));
}
export default dispatchForm;
Now, at runtime, I need to fetch values at button submit, and render another pug template, with "rollup-plugin-pug".
I'm trying to create another JavaScript module for this handling and rendering:
calculateButton.js
// modules/calculateButton.js
import templateFn from '../../templates/partials/field.pug';
const button = document.querySelector('[data-calculator-button]');
button.addEventListener('click', (e) => {
if (e.target.getAttribute('disabled')) return;
const gain = document.querySelector('[data-calculator-form][name="gain"]:checked').value;
const cost = document.querySelector('[data-calculator-form][name="cost"]:checked').value;
document.getElementById("result").innerHTML = templateFn({
gain, cost
});
console.log(templateFn())
});
and
field.pug
//- template.pug
.content
p= gain
p= cost
But nothing renders when I click the button and submit my values. All I see at browser url if I click at 130 radio is:
http://localhost:3000/?gain=points&cost=130
I've followed this documentation: https://www.npmjs.com/package/rollup-plugin-pug
But no rendering. What am I missing? Any ideas? How do I console.log this clicking at least so I can check it?

How to connect to Graphql server using Ember-Apollo?

I am working with a full stack GraqlQL based application. The server is working fine and now I need to try out the first queries and mutations on the client side. For some reason, the "monitoring" route, and everything that follows it, is not displayed. Below I will show the files that I have edited or created.
items.graphql:
query {
items {
_id
name
}
}
environment.js:
'use strict';
module.exports = function(environment) {
let ENV = {
apollo: {
apiURL: 'http://localhost:5000/graphql'
},
modulePrefix: 'client',
environment,
rootURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
//
},
EXTEND_PROTOTYPES: {
Date: false
}
},
APP: {
//
}
};
if (environment === 'development') {
//
}
if (environment === 'test') {
ENV.locationType = 'none';
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
}
if (environment === 'production') {
//
}
return ENV;
};
monitoring.js (route):
import Route from '#ember/routing/route';
import { queryManager } from 'ember-apollo-client';
import query from 'client/gql/items.graphql';
export default Route.extend({
apollo: queryManager(),
model() {
return this.apollo.watchQuery({ query }, 'items');
}
});
monitoring.hbs:
<h3>Monitoring</h3>
<div>
{{#each model as |item|}}
<h3>{{item.name}}</h3>
{{/each}}
</div>
{{outlet}}
Thank you for attention!
I see this error:
Uncaught (in promise) Error: fetch is not defined - maybe your browser targets are not covering everything you need?
The solution is to fix two things.
First is to put this in ember-cli-build.js:
'ember-fetch': {
preferNative: true
}
And fix the route file:
import Route from '#ember/routing/route';
import { queryManager } from 'ember-apollo-client';
import query from 'client/gql/queries/items.graphql';
export default Route.extend({
apollo: queryManager(),
async model() {
let queryResults = await this.apollo.watchQuery({ query }, 'items');
return Object.values(queryResults);
}
});

How to read json file in typescript?

I am trying to get object from api.json but it throws error , based on typescript i have added declare module "*.json" into the project , Any idea how can i achieve this task ?
api.json
{
"Refills": {
"getPatientInfo": "Refills/patientInfo/GetPatientInfo"
}
}
index.ts
import {ModuleExecutor} from "./common/ModuleExecutor";
import {Identity} from "./common/Enums";
export class Index {
private executor: ModuleExecutor = null;
// Any string prepended with # is handled by grunt before building the project
// grunt dynamically reads the config/api.json and loads only the apis that are listed there
// api.json consists of the API name and the folder path for grunt
private _apisConfig: string = '#api'
constructor(identity: string) {
this.executor = new ModuleExecutor(Identity[identity]);
const apiConfig = JSON.parse(this._apisConfig);
console.log('API', apiConfig);
for (const module in apiConfig) {
if (apiConfig.hasOwnProperty(module)) {
this[module] = {};
for (const api in apiConfig[module]) {
if (apiConfig[module].hasOwnProperty(api)) {
this[module][api] = this.executor.execute(apiConfig[module][api]);
}
}
}
}
}
}
Error
SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
Compiled index.js file
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ModuleExecutor_1 = require("./common/ModuleExecutor");
const Enums_1 = require("./common/Enums");
class Index {
constructor(identity) {
this.executor = null;
this._apisConfig = '';
this.executor = new ModuleExecutor_1.ModuleExecutor(Enums_1.Identity[identity]);
const apiConfig = JSON.parse(this._apisConfig);
console.log('API', apiConfig);
for (const module in apiConfig) {
if (apiConfig.hasOwnProperty(module)) {
this[module] = {};
for (const api in apiConfig[module]) {
if (apiConfig[module].hasOwnProperty(api)) {
this[module][api] = this.executor.execute(apiConfig[module][api]);
}
}
}
}
}
}
exports.Index = Index;
//# sourceMappingURL=index.js.map

Categories