vue3 i18n lazy loading not loading text - javascript

I have installed i18n in vue 3 https://vue-i18n.intlify.dev/introduction.html and I want to load the json files lazy.
In the app.js
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: 'en',
fallbackLocale: 'en'
})
const app = createApp(App)
app.use(i18n)
app.mount('#app');
In the main component I have a select
<select class="form-control" #change="onChangeLocale" v-model="selectedLocale">
<option v-for="locale in availableLocales" :key=`locale-${locale}` :value="locale" v-text="locale.toUpperCase()">
</select>
and a text
{{ $t("hello_world") }}
which is in every files in locales (en.json, ro.json, de.json)
ex for en.json
{
"hello_world": "Hello World"
}
In setup
setup() {
const {t, locale, i18n} = useI18n({useScope: 'global'})
const selectedLocale = ref(locale.value)
const availableLocales = ref(['en', 'ro', 'de'])
const onChangeLocale = async () => {
const messages = await import(
/* webpackChunkName: "locale-[request]" */ `../../locales/${selectedLocale.value}.json`
)
// set locale and locale message
i18n.setLocaleMessage(selectedLocale.value, messages.default)
}
}
When loadin the app i see the key for the text "hello_world" - with the message in console (not found "hello_world" key in "en" locale messages) and when changing the select for another language I get en error - cannot read property of undefined (reading 'setLocaleMessage)'
Any idea what I did wrong ?
If setting the messages in app.js like this:
const messages = {
en: {
hello_world: 'Hello World'
},
de: {
...
}
}
then set the messages in i18n options it works.
But I want to load the json files with the translations when selecting the language
https://vue-i18n.intlify.dev/guide/advanced/lazy.html
Any idea ?
*** UPDATE ***
It seems to work after making some changes:
load default language en.json in app.js
import enLocaleMessages from './locales/en.json'
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: 'en',
fallbackLocale: 'en',
messages: {en: enLocaleMessages} // the new change
})
in the component where the locale changes
const {t, locale, setLocaleMessage} = useI18n({useScope: 'global'})
const onChangeLocale = async () => {
const messages = await import(
/* webpackChunkName: "locale-[request]" */ `../../locales/${selectedLocale.value}.json`
)
setLocaleMessage(selectedLocale.value, messages.default)
locale.value = selectedLocale.value
document.documentElement.lang = selectedLocale.value
axios.defaults.headers.common['Accept-Language'] = locale.value
}
I am not sure if it's the correct way ....but for the moment works.

Related

Getting ChunkLoadError: Loading chunk when lazy laoding Translation in vuejs

I am trying to implement Lazy load translations for my Vuejs application and following this guide: https://kazupon.github.io/vue-i18n/guide/lazy-loading.html
Any help will be much appreciated. Thanks In Advance!
File structure is something like this:
src/translations
-- AllMessages.ts
-- bg-BG.json
-- da-DK.json
-- en.json
....... more json files here
my i18n.ts file:
import Vue from "vue";
import VueI18n from "vue-i18n";
// import { allMessages } from "#/translations/allMessages";
Vue.use(VueI18n);
const selectedLanguage: any = 'en' || something.language.split("-")[0];
const loadedLanguages = ["en"];
const getLanguageMessage = async () =>
await import(/* webpackChunkName: "selectedLanguage-[request]" */ `#/translations/${selectedLanguage}.json`);
const messages: any = { [selectedLanguage]: getLanguageMessage() };
console.log("messages", messages); // this promise is rejected, I see in the console
const i18n = new VueI18n({
locale: selectedLanguage, // "en",
fallbackLocale: "en",
messages: messages, // set locale messages
});
console.log("i18n-message", i18n.messages);
export default i18n;
In my console I am getting error:
Probaria cambiar el import por un fetch.

Cannot pass generated nonce in Koa layout

i have a problem with passing generated nonce into my layout.ejs in koa, helmet.
const directives = helmet.contentSecurityPolicy.getDefaultDirectives()
delete directives['form-action']
directives['script-src'] = [
"'self'",
CTFS_BASE_URI,
'https://cdn.cookielaw.org',
(req, res) => {
return `'nonce-${res.locals.cspNonce}'`
}
]
const pHelmet = promisify(
helmet({
contentSecurityPolicy: {
useDefaults: false,
directives
}
})
)
provider.use(async (ctx, next) => {
const origSecure = ctx.req.secure
ctx.req.secure = ctx.request.secure
// eslint-disable-next-line no-unused-expressions
ctx.res.locals || (ctx.res.locals = {})
ctx.res.locals.cspNonce = crypto.randomBytes(16).toString('base64')
await pHelmet(ctx.req, ctx.res)
ctx.req.secure = origSecure
return next()
})
render(provider.app, {
cache: false,
viewExt: 'ejs',
// TODO(#ct): comment layout if you don't want to use it.
layout: '_layout',
root: path.join(__dirname, 'views')
})
My configuration looks like this. provider variable is oidc-provider library.
helmet is from "helmet" and not from "koa/helmet" i tried "koa/helmet" but it doesn't work either.
I am trying to generate nonce and save it into my ctx.res.local.cspNonce. That part works as expected. Then in my _layout.ejs i want to use cspNonce in my script with
<script src="flksdjfsal" <%= locals.cspNonce%>
But it doesn't work. cspNonce variable is empty.
Configuration is from this example https://github.com/panva/node-oidc-provider/blob/main/certification/oidc/index.js
Can someone help me? Thanks

How do I access environment variables in Strapi v4?

Strapi Version: 4.3.0
Operating System: Ubuntu 20.04
Database: SQLite
Node Version: 16.16
NPM Version: 8.11.0
Yarn Version: 1.22.19
I have created Preview button for an article collection type. I'm using the Strapi blog template. I managed to make the Preview button appear in the Content Manager. I hard coded the link to be opened when you click the Preview button and it works. Now, I want the plugin to use a link with environment variables instead of a hard coded link. I don't know how I can access the environment variables in the source code for the plugin.
My objective:
I want to replace
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
with
href={${CLIENT_FRONTEND_URL}?secret=${CLIENT_SECRET}&slug=${initialData.slug}`}
in ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
where CLIENT_FRONTEND_URL and CLIENT_SECRET are environment variables declared like so in .env:
CLIENT_FRONTEND_URL=http://localhost:3000
CLIENT_PREVIEW_SECRET=abc
Here's a rundown of the code I used:
First, I created a strapi app using the blog template, then created a plugin.
// Create strapi app named backend with a blog template
$ yarn create strapi-app backend --quickstart --template #strapi/template-blog#1.0.0 blog && cd backend
// Create plugin
$ yarn strapi generate
Next, I created a PreviewLink file to provide a link for the Preview button
// ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
import React from 'react';
import { useCMEditViewDataManager } from '#strapi/helper-plugin';
import Eye from '#strapi/icons/Eye';
import { LinkButton } from '#strapi/design-system/LinkButton';
const PreviewLink = () => {
const {initialData} = useCMEditViewDataManager();
if (!initialData.slug) {
return null;
}
return (
<LinkButton
size="S"
startIcon={<Eye/>}
style={{width: '100%'}}
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
variant="secondary"
target="_blank"
rel="noopener noreferrer"
title="page preview"
>Preview
</LinkButton>
);
};
export default PreviewLink;
Then I edited this pregenerated file in the bootstrap(app) { ... } section only
// ./src/plugins/previewbtn/admin/src/index.js
import { prefixPluginTranslations } from '#strapi/helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import Initializer from './components/Initializer';
import PreviewLink from './components/PreviewLink';
import PluginIcon from './components/PluginIcon';
const name = pluginPkg.strapi.name;
export default {
register(app) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: name,
},
Component: async () => {
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
return component;
},
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugin::plugin-name.actionType
// subject: null,
// },
],
});
app.registerPlugin({
id: pluginId,
initializer: Initializer,
isReady: false,
name,
});
},
bootstrap(app) {
app.injectContentManagerComponent('editView', 'right-links', {
name: 'preview-link',
Component: PreviewLink
});
},
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map(locale => {
return import(
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);
return Promise.resolve(importedTrads);
},
};
And lastly this created this file to enable the plugin Reference
// ./config/plugins.js
module.exports = {
// ...
'preview-btn': {
enabled: true,
resolve: './src/plugins/previewbtn' // path to plugin folder
},
// ...
}
I solved this by adding a custom webpack configuration to enable Strapi's admin frontend to access the environment variables as global variables.
I renamed ./src/admin/webpack.example.config.js to ./src/admin/webpack.config.js. Refer to the v4 code migration: Updating the webpack configuration from the Official Strapi v4 Documentation.
I then inserted the following code, with help from Official webpack docs: DefinePlugin | webpack :
// ./src/admin/webpack.config.js
'use strict';
/* eslint-disable no-unused-vars */
module.exports = (config, webpack) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
config.plugins.push(
new webpack.DefinePlugin({
CLIENT_FRONTEND_URL: JSON.stringify(process.env.CLIENT_FRONTEND_URL),
CLIENT_PREVIEW_SECRET: JSON.stringify(process.env.CLIENT_PREVIEW_SECRET),
})
)
return config;
};
I rebuilt my app afterwards and it worked.
You shouldn't have to change the webpack config just find .env file in the root directory
add
AWS_ACCESS_KEY_ID = your key here
then just import by
accessKeyId: env('AWS_ACCESS_KEY_ID')

Vue 3 Dynamic import files

I'm trying to build Vue 3 dynamic loading JSON files, in order to create a website builder. So each page is defined as a JSON file, in order to allow the user to change the JSON file from the backend (all components are defined in that file).
When I'm loading every other route than auth, it's working fine, but Auth is loading twice (the load_components function).
Here is the example of the routes. Most of the pages will have a loadParent component, but there are a few that will not. And loadParent for now has only <router-view /> in it:
// Simple page
{
path: "/home",
name: "home",
component: () => import("../loadPage.vue"),
},
// Pages with parent
{
name: 'auth',
path: '/auth',
meta: {
title: "Some users" ,
adminOnly: true
},
component: () => import("../loadParent.vue"),
children: [
{
path: '',
name: 'login',
props: (route: any) => ({}),
component: () => import("../loadPage.vue"),
}
]
}
So here is how template looks like:
<template>
<component v-for="block in components" :is="{...block.component}" :key="Math.random()" :vars="block.props"></component>
</template>
And in the setup script I have defined ref where is stored path of the file, like:
const json: any = ref();
Because every file is defined as route name, I'm using import in setup:
json.value = import('../pages/' + route.value + '.json');
So here is how the function how I'm actually loading the components:
const load_components = async () => {
console.log('load page components ')
// Get file for load
Promise.resolve(json.value).then(function (e: any) {
// Reset previous page pages
components.value = [];
// For each component for the page and register them under new const of the pages
e.default.components.forEach((c: any) => {
components.value.push({
"component": defineAsyncComponent(() => import('./templates/' + template.value + '/' + c.component)),
"props": c.props
})
})
}).catch((e: any) => {
// If component can't be loaded - Delete for production
console.log(e + ' cannot be loaded!');
});
}
So in order to trigger the function, I'm doing something like:
onBeforeMount(async() => {
json.value = import('../pages/' + route.value + '.json');
// Initiate component load
await load_components()
})
// /** Watch when client changes a page to load new json for next page - This function is for pages that has same component */
watch(router, async (value: any) => {
json.value = import('../pages/' + value.name + '.json');
// There is no need for that if I load it really dynamically
await load_components()
})
Maybe just the approach is not correct, what do you think?

React i18next return a fallback language

I'm using i18next in my React app in this way to translate files:
i18next.js (original)
import i18next from 'i18next';
import XHR from 'i18next-xhr-backend';
import detector from 'i18next-browser-languagedetector';
window.userLang = navigator.language || navigator.userLanguage;
i18next
.use(detector)
.use(XHR)
.init({
lng: window.userLang,
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false,
},
});
export default i18next;
Using the code above, I am able to successfully translate pages based on the user's browser data. If the language is not available, it falls back to English as intended.
However, I ran into some components which were not translated into the keys provided. They are set up in the following way in the menu component:
Menu Component
const projectArchives = i18next.t('LEFT_MENU.projectArchives');
export default {
[KEY_ARCHIVE_MODEL]: {
en: projectArchives,
it: projectArchives,
},
Output in browser:
LEFT_MENU.projectArchives
I changed my i18next.js file to have a variable (var data =) which would get the language from a string:
i18next.js (Updated)
import i18next from 'i18next';
import XHR from 'i18next-xhr-backend';
import detector from 'i18next-browser-languagedetector';
window.userLang = navigator.language || navigator.userLanguage;
let language = window.userLang.split('-')[0];
var data = require(`../../public/locales/${language}/translation.json`);
const FALLBACK_LOCALE = 'en';
i18next
.use(detector)
.use(XHR)
.init({
lng: window.userLang,
fallbackLng: FALLBACK_LOCALE,
debug: true,
resources: {
otherLanguages: FALLBACK_LOCALE,
it: {
translation: data,
},
en: {
translation: data,
},
},
});
export default i18next;
Now, the menu is able to display the translated keys but now, if a language is not available as in the original code, the following error is returned:
Error: Cannot find module './fr/translation.json'.
because there is ofcourse there is not an available French translation file.
The error occurs because of the following line:
var data = require(`../../public/locales/${language}/translation.json`);
I've tried connecting the FALLBACK_LOCALE variable directly to the English translation file but this also failed.
var FALLBACK_LOCALE = require(`../../public/locales/en/translation.json`);
How can I force the fallback language or create a conditional for the expected behavior?
import i18next from 'i18next';
import XHR from 'i18next-xhr-backend';
import detector from 'i18next-browser-languagedetector';
window.userLang = navigator.language || navigator.userLanguage;
let language = window.userLang.split('-')[0];
const FALLBACK_LOCALE = 'en';
i18next
.use(detector)
.use(XHR)
.init({
lng: window.userLang,
fallbackLng: FALLBACK_LOCALE,
debug: true,
resources: {
otherLanguages: FALLBACK_LOCALE,
it: {
translation: require(`../../public/locales/it/translation.json`),
},
en: {
translation: require(`../../public/locales/en/translation.json`),
},
},
});
export default i18next;

Categories

Resources