Big Locale DBs in React-native (Expo) - react-native

I've found this good and simple article https://medium.com/#jamuhl/translate-your-expo-io-react-native-mobile-application-aa220b2362d2 to implement localization in RN (in Expo).
My use case is a bit different: my app have list of hundrends of terms to translate (a js object of ~1MB each language, times 6-7 languages).
The app is totally offline (no chance to load the locale files from a server) so I'm searching the best way to lazy-loads the json/js locale object I want.
I'm here to take advices from RN/i18next experts possibly.
EDIT:
I pretty much copied the i18n configuration of this https://github.com/i18next/react-i18next/blob/master/example/react-native-expo/js/i18n.js
The real databases are in the field data:DB. Obiouvsly that's not the best way to load heavy files, I always loads all the DB of all the languages..can I keep somehow the same simple structure but lazy-load just the language I need?
Otherwise, there is an example of local lazy-load (from the device file system) in i18next and react-native?
import i18n from 'i18next';
import {reactI18nextModule} from 'react-i18next';
i18n
.use(languageDetector)
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
resources: {
en: {
home: {
title: 'Welcome',
introduction: 'This text comes from i18next and is provided in english.',
},
data: { DB:require("./locales/it.json") },
},
de: {
home: {
title: 'Willkommen',
introduction: 'Dieser Text ist von i18next und ist in deutsch.',
},
data: { DB:require("./locales/de.json") },
}
},
// have a common namespace used around the full app
ns: ['common'],
defaultNS: 'home',
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it does escape per default to prevent xss!
}
});

With i18next it's rather simple to create a own backend implementation...https://www.i18next.com/misc/creating-own-plugins#backend
I would suggest bundling your translations with the application and not loading from any external resource. You also can directly add translations on the fly using https://www.i18next.com/overview/api#resource-handling

Related

How to use vue component across multiple node projects?

I'm trying to build a website builder within the drag-and-drop abilities via using Vue3. So, the user will be playing with the canvas and generate a config structure that going to post the backend. Furthermore, the server-side will generate static HTML according to this config.
Eventually, the config will be like the below and it works perfectly. The config only can have HTML tags and attributes currently. Backend uses h() function to generate dom tree.
My question is: can I use .vue component that will generate on the server side as well? For example, the client-side has a Container.vue file that includes some interactions, styles, etc. How can the backend recognize/resolve this vue file?
UPDATE:
Basically, I want to use the Vue component that exists on the Client side on the backend side to generate HTML strings same as exactly client side. (including styles, interactions etc).
Currently, I'm able to generate HTML string via the below config but want to extend/support Vue component itself.
Note: client and server are completely different projects. Currently, server takes config and runs createSSRApp, renderToString methods.
Here is the gist of how server would handle the API:
https://gist.github.com/yulafezmesi/162eafcf7f0dcb3cb83fb822568a6126
{
id: "1",
tagName: "main",
root: true,
type: "container",
properties: {
class: "h-full",
style: {
width: "800px",
transform: "translateZ(0)",
},
},
children: [
{
id: "9",
type: "image",
tagName: "figure",
interactive: true,
properties: {
class: "absolute w-28",
style: {
translate: "63px 132px",
},
},
},
],
}
This might get you started: https://vuejs.org/guide/scaling-up/ssr.html#rendering-an-app
From the docs:
// this runs in Node.js on the server.
import { createSSRApp } from 'vue'
// Vue's server-rendering API is exposed under `vue/server-renderer`.
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button #click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
console.log(html)
})
I guess extract the template from request or by reading the submitted Vue file and use that as the template parameter value

Sharing i18next instance between applications without override

I'm currently working on internationalizing a system built with single-spa (microfrontends) with applications written on Angular and React.
I started using i18next and it's going pretty well, however, I've found a problem when trying to share the i18next dependency between all the applications.
When two applications are mounted simultaneously on the view the one who loads last overrides the i18next instance and thus the translations for the first one are never found as they were not loaded on the latter.
Thanks in advance!
It is better that I18next will be initialized at the shell level with the shell namespaces, and each internal spa will add its namespaces to the shared instance.
This way you won't have duplication of instance & code.
You can use [i18next.addResourceBundle][1] in order to add translation resources that are related to the current inner app.
i18next.addResourceBundle('en', 'app1/namespace-1', {
// ----------------------------------^ nested namespace allow you to group namespace by inner apps, and avoid namespace collisions
key: 'hello from namespace 1'
});
Pass the i18next instance as props to the inner app.
// root.application.js
import {i18n} from './i18n';
// ------^ shells i18next configured instance
singleSpa.registerApplication({
name: 'app1',
activeWhen,
app,
customProps: { i18n, lang: 'en' }
});
// app1.js
export function mount(props) {
const {i18n, lang} = props;
i18n.addResourceBundle(lang, 'app1/namespace-1', {
key: 'hello from namespace 1',
});
return reactLifecycles.mount(props);
}
Hope that the idea is clear :]
[1]: https://www.i18next.com/how-to/add-or-load-translations#add-after-init

How do I translate Quasar component?

I have a Vue/Quasar application in which I use i18n but now I'm facing a problem: table footer doesn't get translated. In the table headers, for example, I do something like this to translate column names:
{
align: 'left',
field: (val) => val,
label: this.$t('locale.column'),
name: 'column',
required: true,
sortable: true,
},
where $t is 18n function, locale is my component and the column is actually's column's name. I don't have direct access to the footer of the table (where the pagination an the total number of the elements are) and it doesn't get translated. I also use Quasar language packs, quasar.js goes like this:
import en from 'quasar/lang/en-us.js'
/*
import other languages
*/
import {
state,
} from '#state/modules/i18n';
const locale = state.locale;
const langDictionary = {
// all the imported langs go here
};
Vue.use(Quasar, {
config: {},
components: { /* not needed if importStrategy is not 'manual' */ },
directives: { /* not needed if importStrategy is not 'manual' */ },
plugins: {
Cookies,
Dialog,
Loading,
Notify,
},
lang: langDictionary[locale]
});
export {langDictionary};
You are probably setting the language at startup correctly as lang: langDictionary[locale]. If you change the language state in your app later, you need to also inform Quasar if you want Quasar components and plugins to get correctly translated. See the docs, Change Quasar Language Pack at Runtime for how to achieve that, the docs about language pack also includes tips for dynamically loading the language pack.
You didn't specify how you change the language in your app, so I can't give an example built upon that.

vue and webpack doesn't do lazy loading in components?

The lazy component in vue/webpack seem to be wrong or I miss confuse about the terms.
To do lazy loading of component I use the keyword import and webpack should split this component to sepeate bundle, and when I need to load this component webpack should take care of it and load the component.
but in fact, webpack does make sperate file, but it loaded anyway when the application is running. which is unexpected.
For example I just create a simple vue application (using the cli) and browse to localhost:8080/ and the about page should be loaded (by default) using the import keyword.
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
So This is by design? I load every time the file I do not need right now (the page about). and if I have 200 pages, when I'll fetch 200 files I dont need. how that ends up? that did I save here by using the import key?
In vuetify for example they uses import key, but the files are loaded anyway and not by demand.
You can also avoid component prefetch using one of the webpack "magic" comments (https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules), eg.
components: {
MyComponent: () => import(/* webpackPrefetch: false */ './MyComponent.vue')
}
Feel free to read more about this Vue optimization below:
https://vueschool.io/articles/vuejs-tutorials/lazy-loading-individual-vue-components-and-prefetching/
If you're referring to the default app template from Vue CLI, then you're actually observing the prefetch of the About page, intended to reduce load times for pages the user will likely visit.
If you want to avoid this performance optimization, use this Vue config:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.plugins.delete('prefetch')
config.plugins.delete('preload')
}
}
For troubleshooting reference, Chrome's Network panel includes an Initiator column, which provides a clickable link to the source file that triggered the network call. In this case of the about.js, the source file looks like this:
try using vue-lazyload maybe it can help and for <script> tags you can try async defer it helps in website speed optimizations

Vue Lazy load refactor

I am trying to lazy load a lot of components in my app, I want to show a loading spinner while any of them is loading, and the same for error; so there is a lot of duplication.
export default c => ({
component: import(`${c}`),
loading: loadingComponent,
timeout: 3000
})
I am trying to refactor this into a single function and using it like that
import lazyload from './lazyload';
Collection: lazyload("./Collection.vue")
But webpack is not extracting the component as it normally does, I know that I am missing something.
You need to be creating an async component factory (meaning function). Also the import module cannot be fully dynamic, there needs to be some prefix to the module path otherwise it could match literally any module and webpack needs to know which subset of modules it could possibly match at runtime to include them in the build.
Fully dynamic statements, such as import(foo), will fail because webpack requires at least some file location information. This is because foo could potentially be any path to any file in your system or project. The import() must contain at least some information about where the module is located, so bundling can be limited to a specific directory or set of files.
I've made some adjustments to your code (untested):
lazyload.js
export default c => () => ({
component: import(`./components/${c}`),
loading: loadingComponent,
timeout: 3000
})
Example usage
import lazyload from './lazyload'
export default {
components: {
Collection: lazyload('collection.vue')
}
}
A better implementation, in my opinion, would be to avoid creating the dynamic import. I prefer webpack to know for certain which modules are definitely needed at build time instead of bundling together a subset of modules inside a directory.
lazyload.js
export default componentFn => () => ({
component: componentFn(),
loading: loadingComponent,
timeout: 3000
})
Example usage
import lazyload from './lazyload'
export default {
components: {
Collection: lazyload(() => import('./collection.vue'))
}
}
Now lazyload has no dependency on any specific component directory and can be used with any component.