VueJS i18n - How to change the variable prefix in translation files - laravel

I'm doing an app using Laravel inertia and Vue, we wanted to add i18n to the app and use the same translation files for both laravel and i18n, the problem is the variable interpolation, laravel by default use :variable but vue i18n use {variable}
I tried to create a custom formatter based on what i've found here but using a custom formatter seems deprecated since I have this in my console: [intlify] Not supported 'formatter'.
I've seen on the official i18n doc that i18n is supposed to have an option for both prefix and suffix for the variable interpolation but it does not seems to exists in vue-i18n.
Does anyone ever experienced this or have an idea to solve this 'issue' ?

I run a for loop in my translation jsons and converted :variable syntax to {variable}
import {createI18n} from 'vue-i18n'
import zhCN from '../lang/zh-CN.json';
export function initializeI18n() {
let en = {};
Object.entries(zhCN).forEach(([key, value]) => {
let newEnValue = key;
en[key] = key;
if (value.includes(':')) {
let arr = value.match(/\B\:\w+/ig);
arr.forEach(matchedStr => {
let cleanMatchedStr = matchedStr.replace(':', '')
value = value.replace(matchedStr, `{${cleanMatchedStr}}`)
newEnValue = newEnValue.replace(matchedStr, `{${cleanMatchedStr}}`)
})
zhCN[key] = value;
en[key] = newEnValue;
}
})
return createI18n({
locale: window.__DEFAULT_LOCALE__,
fallbackLocale: window.__FALLBACK_LOCALE__,
messages: {
'zh-CN': zhCN,
en
},
silentFallbackWarn: true,
silentTranslationWarn: true
});
}
This was, putting in es.js
"Hello :name": "Hola :name"
should work as expected:
{{ $t("Hello :name", {name: 'world'}) }},

Related

Is it possible to use the layout in the _app.jsx component with next-i18next?

To create a site, I use nextjs, when creating pages, I took the general layout with the header and footer into a separate hoc component and wrapped the page components in the file with it _app.jsx:
function App({ Component, ...rest }) {
const { store, props } = wrapper.useWrappedStore(rest)
return (
<Provider store={store}>
<Layout>
<Component {...props.pageProps} />
</Layout>
</Provider>
)
}
Everything worked fine until localization became a problem, after using the next-18next library for translations and adding serverSideTranslations, two errors began to appear on each page:
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
frontend-node_1 | TypeError: Cannot read properties of undefined (reading 'label')
frontend-node_1 | at DropdownSwitcher (webpack-internal:///./src/components/header/translation/DropdownSwitcher.jsx:45:36)
frontend-node_1 | at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5658:16)
frontend-node_1 | at renderIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5731:15)
frontend-node_1 | at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5946:7)
frontend-node_1 | at renderMemo (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5868:3)
frontend-node_1 | at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6011:11)
frontend-node_1 | at renderNodeDestructiveImpl (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6104:11)
frontend-node_1 | at renderNodeDestructive (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6076:14)
frontend-node_1 | at renderNode (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6259:12)
frontend-node_1 | at renderHostElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5642:3)
The error with "label" occurs because the i18n object is empty on the server:
const DropdownSwitcher = () => {
const { i18n } = useTranslation()
const currentLanguage = useMemo(() => { // language as undefined
return LANGUAGES.find((item) => item.language === i18n.language)
}, [i18n.language])
....
But everything is fine on the client and there are no errors. What could be the reason and how to fix it, since the App itself from the _app.jsx file is wrapped in appWithTranslation from next-i18next.
Therefore, two questions arise, how to fix react-i18next:: You will need to pass in an i18next instance by using initReactI18next and why there is no i18n object on the server?
I moved the layout to the level of the page itself, removing it from _app.js, but for some reason, then something, useEffect() is repeated in the header, although the header component has not changed in any way and bringing the layout to the level of _app.jsx fixes it
If there is not enough information or you need a visual example, I will try to create a small program that demonstrates this with open source. Please write in a comment.
I solved my problem, but I forgot to provide an answer here, but I noticed that someone also has this problem, so I will try to help people who come across this post, although it is relevant only for nextjs version 12, since with the appearance of version 14, the structure there has improved a lot with as I think there should be no more questions like mine.
1. Rendering the layout
In the official doc, there is a whole section that describes how to correctly divide the layout so that it works according to the SPA type.
pages/index.jsx
// pages/index.jsx
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
export default function Page() {
return (
/** Your content */
)
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
pages/_app.js
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
This component method approach is much better than using its direction in _app.jsx because you can extend or replace them and not make a crude monolith, example how I used it:
// pages/ingex.jsx
function HomePage() {
return (
<HomeLayout>
<Main />
</HomeLayout>
)
}
HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
// pages/about-us.jsx
const AboutUsPage = () => {
return (
<>
<HomeLayout>
<AboutUs />
</HomeLayout>
</>
)
}
AboutUsPage.getLayout = (page) => (
<MainLayout withNav>
<LayoutContext.Consumer>
{({ device }) => device.isMobile && <NavigationMobile />}
</LayoutContext.Consumer>
{page}
</MainLayout>
)
With this approach, react still works like a spa and a similar page to about-us, which will also have NavigationMobile, will simply compare it.
2. Error with next-i18next
The whole point was that the next-i18next library was configured incorrectly in the first place (more precisely, it needed to be corrected). In order to configure everything correctly, I had to do the following:
- Move the folder with translation files to the public folder. This is necessary so that the library config, which we will configure a little below, can see the translation files and interact with them
- Configure next-i18next.config.js to work with the client. Here is an example setup with some comments. And also a link to the documentation, and some other resources I found while setting up.
next-i18next.config.js
const path = require('path')
const LANGUAGES = ['en', 'pl', 'uk']
const DEFAULT_LANGUAGE = 'en'
// if it is the server, then the full path, if the client, then the relative path.
const localePath =
typeof window === 'undefined' ? path.resolve('public', 'translation') : '/public/translation'
module.exports = {
i18n: {
defaultLocale: DEFAULT_LANGUAGE,
locales: LANGUAGES,
fallbackLng: LANGUAGES,
nsSeparator: '::',
keySeparator: '::',
// How to use libraries for i18next like LanguageDetector
use: [require('i18next-intervalplural-postprocessor')],
serializeConfig: false,
},
localePath: localePath,
}
- Configure next-i18next in the _app.jsx file. Here everything is as described in the documentation.
import { appWithTranslation } from 'next-i18next'
import nextI18NextConfig from '../../next-i18next.config'
function App({ Component, ...rest }) {
const { store, props } = wrapper.useWrappedStore(rest)
const getLayout = Component.getLayout || ((page) => page)
//WARNING!!! You don't have to have your own i18next initialization like i18next.use(LanguageDetector).use(intervalPlural).init({ detection: options }) this is all done by the next-i18next library
return (
<Provider store={store}>
<AppHOC>{getLayout(<Component {...props.pageProps} />)}</AppHOC>
</Provider>
)
}
export default appWithTranslation(App, nextI18NextConfig)
- You need to pass the config when calling the serverSideTranslations function. To make your life easier, it is better to transfer the implementation of this function to another file, here is an example of how I did it:
// utils/serverSideTranslations.js
import { serverSideTranslations as baseServerSideTranslations } from 'next-i18next/serverSideTranslations'
import { dt } from '../../constants/defaultTranslate'
import { DEFAULT_LANGUAGE } from '../../constants/languages'
import nextI18NextConfig from '../../../next-i18next.config.js'
const serverSideTranslations = async (locale, domains = []) => {
return await baseServerSideTranslations(locale, [...dt, ...domains], nextI18NextConfig, [
DEFAULT_LANGUAGE,
])
}
export default serverSideTranslations
- And finally, use this function on the pages.
import MainLayout from '../components/layouts/MainLayout'
import serverSideTranslations from '../utils/serverSideTranslations'
import HomeLayout from '../components/home/HomeLayout'
import Main from '../components/home/main/Main'
function HomePage() {
return (
<HomeLayout>
<Main />
</HomeLayout>
)
}
HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
export const getServerSideProps = async ({ locale }) => {
// Wrapping in Promis.all is not necessary, I use it simply so that if there are any other asynchronous operations, then not to use them through await and not to block each other's work
const [translations] = await Promise.all([
serverSideTranslations(locale, ['home']),
])
return {
props: {
...translations,
},
}
}
export default HomePage
I hope this helped someone, if you have any comments, write in the comments

ERR_TOO_MANY_REDIRECTS on vercel after deploying Nextjs App

I deployed my nextjs app to Vercel and it was a success.
I can see a preview of the website and even the log saying it works well.
But i try to access the website via the default Vercel domain :
https://tly-nextjs.vercel.app/
I get an ERR_TOO_MANY_REDIRECTS.
I do not have this problem locally.
I tried :
Disabling a language redirect that I use (/en for english folks, and / for french people).
Disabling the language detector of i18next.
But none of these solutions changed anything.
Any ideas ?
i18n.js file
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Cache from 'i18next-localstorage-cache';
import LanguageDetector from 'i18next-browser-languagedetector';
const fallbackLng = ['fr'];
const availableLanguages = ['fr', 'en'];
const en = require('./locales/en/common.json');
const fr = require('./locales/fr/common.json');
const options = {
order: ['querystring', 'navigator'],
lookupQuerystring: 'lng'
}
const cacheOptions = {
// turn on or off
enabled: true,
// prefix for stored languages
prefix: 'i18next_res_',
// expiration
expirationTime: 365*24*60*60*1000,
// language versions
//versions: {}
}
i18n
.use(Cache)
.use(initReactI18next)
.use(LanguageDetector)
.init({
cache: cacheOptions,
fallbackLng: fallbackLng,
debug: true,
detection: options,
supportedLngs: availableLanguages,
nonExplicitSupportedLngs: true,
resources: {
en: {translation: en},
fr: {translation: fr},
},
interpolation: {
escapeValue: false,
},
react: {
wait: true,
useSuspense: true,
},
});
export default i18n;
My change Language function :
const changeLanguageInHouse = (lang, bool) => {
setLoading(true);
i18next.changeLanguage(lang).then((t) => {
setLanguage(lang);
bake_cookie("langChoice", lang);
setLoading(false);
if (bool === true) {
var newUrl2 = (lang === "fr" ? "" : "/en") + asPath;
window.location.replace(newUrl2);
}
});
};
What happend at your server is following:
You enter https://tly-nextjs.vercel.app/ and it is redirected to /en with HTTP-Status-Code 307 (Temporary Redirect).
And /en redirect with 301 (Permanent Redirect) to /.
You can reproduce this by open the Browser-Dev-Tools and have a look at the Network Tab.
It might be, that you have some kind of url-rewriting activated at your server, which redirect everything to your domain-root.
Is there a public repo available for this? Here is how it worked for me.
Try changing the order of the locales and the default locale (not sure this helps, but it changed something for me. Undocumented if that is the case!)
So I put the default locale first (which is nl for me) in both the locales array and the domains array.
Let me know if that helps!
module.exports = {
i18n: {
localeDetection: false,
// These are all the locales you want to support in
// your application
locales: ['nl', 'en'],
// This is the default locale you want to be used when visiting
// a non-locale prefixed path e.g. `/hello`
defaultLocale: 'nl',
// This is a list of locale domains and the default locale they
// should handle (these are only required when setting up domain routing)
domains: [
{
domain: 'example.be',
defaultLocale: 'nl',
},
{
domain: 'example.com',
defaultLocale: 'en',
},
],
},
trailingSlash: true,
};
I changed all my getInitialProps to getServerSideProps
and realised I was doing a redirect on response :
res.writeHead(301, { Location: "/" })
I just delete it.
And now I don't have this endless redirect.
Doing this worked for me...
https://ardasevinc.tech/cloudflare-vercel-too-many-redirects
I think it's the actual solution to the cause of the problem rather than a bandage!

Cypress - extract URL info

I have this URL :
https://www.acme.com/book/passengers?id=h1c7cafc-5457-4564-af9d-2599c6a37dde&hash=7EPbMqFFQu8T5R3AQr1GCw&gtmsearchtype=City+Break
and want to store these values :
id=h1c7cafc-5457-4564-af9d-2599c6a37dde
hash=7EPbMqFFQu8T5R3AQr1GCw
for use in a later test.
How do I extract these values from the URL? I am using Cypress. Thanks.
Please follow the following steps and that's all there is to it.
You can put this snippet into before() hooks of your spec file and you can access them wherever you want.
cy.location().then(fullUrl => {
let pathName = fullUrl.pathname
let arr = pathName.split('?');
let arrayValues = arr[1].split('&');
cy.log(arrayValues[0]);
cy.log(arrayValues[1]);
cy.log(arrayValues[2]);
})
In case anyone needs the correct answer, use the cy.location('search') to extract the search part of the location data.
Then for convenience, convert it to a javascript object with key/value pairs for each item.
Finally, store it in a Cypress alias to use later in the test.
cy.location('search')
.then(search=> {
const searchValues = search.split('?')[1].split('&')
// yields: [
// id=h1c7cafc-5457-4564-af9d-2599c6a37dde,
// hash=7EPbMqFFQu8T5R3AQr1GCw,
// gtmsearchtype=City+Break
// ]
const searchMap = searchValues.reduce((acc,item) => {
const [key,value] = item.split('=')
acc[key] = value.replace('+', ' ')
return acc
}, {})
// yields: {
// id: "h1c7cafc-5457-4564-af9d-2599c6a37dde",
// hash: "7EPbMqFFQu8T5R3AQr1GCw",
// gtmsearchtype: "City Break"
// }
cy.wrap(searchMap).as('searchMap')
})
Using #Srinu Kodi's answer I got it working changing ...then(fullUrl => ... to
...then((fullUrl) => ...

how to access vue.js api key in laravel application

hello there i am trying to access my youtube api key located in the .env file from within this code:
<template>
<div class="YoutubeDash__wrapper">
<video-group :videos="videos"></video-group>
</div>
</template>
<script>
import VideoGroup from './VideoGroup.vue';
import Search from './Search';
export default {
components: {
VideoGroup
},
created(){
Search({
apiKey: process.env.VUE_APP_SECRET,
term: 'laravel repo'
}, response => this.videos = response);
},
data(){
return {
videos: null
}
}
}
</script>
According to the documentation using env variables with vue.js. Everything seems to be correct. In my .env file i say: VUE_APP_SECRET=xxxxxxxxxxxxxxx, what am i missing here ?
I get this error message:
app.js:37809 Error: YouTube search would require a key
Any tips are welcome! Thanks a lot!
We need to work with a small amount of information here so I am going to make a few assumptions (based on the tags) mostly that you are using laravel and laravel-mix to compile your resources.
For laravel(-mix) to make your .env variables accessible by JS you need to prefix them with MIX_ i.e. MIX_VUE_APP_SECRET. This will make your variable accessible as process.env.MIX_VUE_APP_SECRET.
I prefer excluding laravel-mix from this process.
Usually, in my blade entry-point I use meta tags:
<meta name="myVal" content="{{ config('<any-config-key>') }}">
<any-config-key> can be any laravel configuration key including those taken from .env.
Then, in my javascript I do something like:
const setVueGlobal = (metaHeaderName, vueGlobalName) => {
let value = document.head.querySelector('meta[name="' + metaHeaderName + '"]').content;
if (!value) {
console.error('some error msg');
return null;
}
Vue.prototype[vueGlobalName] = value;
return value;
};
setVueGlobal('myVal', '$myVal');
Finally, accessing using this.$myVal

require handlebar plugin i18n only load default 'en_us.json'

I try to use require-handlebars-plugin with i18n functionality.
But, Even if I change my browser language, require try to load the default file 'en_us.json'.
I can set a locale value in require js config to "force" to load the right file. But I don't think it is the good way to "how use it".
define({
name: 'The Handle of the Bars',
require: {
paths: {
handlebars: 'components/require-handlebars-plugin/Handlebars',
hbs: 'components/require-handlebars-plugin/hbs',
i18nprecompile: 'components/require-handlebars-plugin/hbs/i18nprecompile',
json2: 'components/require-handlebars-plugin/hbs/json2'
},
// locale: (navigator.language.substring(0, 2) === 'en') ? 'en' : 'fr',
hbs: {
disableI18n : false,
disableHelpers: true,
templateExtension: 'hbs'
}
},
What am I missing ? :)
Oh and I have also an other question :
Is it possible to set a default value if the language isn't found ?
For example, if the browser language is set to "de", but My application have only en and fr translations.
Answer to second question:
var availableLocales = ['en', 'fr'];
var browserLocale = navigator.language.substring(0, 2);
var locale = availableLocales.indexOf(browserLocale) >= 0 browserLocale : 'en'; // 'en' is default locale

Resources