How to use a postProcess with i18next? - react-native

From this article I read that you can add a post processing function to i18next:
i18n.addPostProcessor("myProcessorsName", function(value, key, options)
{
return 'some post processed data based on translated value';
});
and add it during initialization:
i18n.init({ postProcess: 'myProcessorsName' });
But I get an error addPostProcessor is not a function.
So how can I add and use a post processing function to i18next?

From the documentation I figured you can create a post process module and add it to the i18next instance with use().
In this example, the post process module will capitalize the first letter of any string returned:
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
(...)
const CapitalizeFirstLetter = (str) => {
return str.length ? str.charAt(0).toUpperCase() + str.slice(1) : str
}
const initTranslations = () => {
i18next
.use({
type: 'postProcessor',
name: 'capitalize',
process: function (value, key, options, translator) {
return CapitalizeFirstLetter(value);
}
})
.use(initReactI18next) // passes i18n down to react-i18next
.init({
postProcess: ["capitalize"]
})
}

Related

Testing custom hooks that are using next-i18next useTranslation hook

I have created a custom hook that returns the translated value using the useTranslation hook.
import { useTranslation } from "next-i18next";
export const useCustomHook = (data) => {
const {t, i18n: { language: locale }} = useTranslation();
const value = {
field: t("some.key.from.json.file", { arg: data.arg }),
field2: data.name,
field3: t("another.key", {
arg: data.arg2, count: 3
})
}
return value;
};
I want to create a unit test for this custom hook, but I can't get the useTranslation hook to work as it does when running the app itself. Further info my current setup is as follows:
1- I'm using Nextjs with next-i18next library.
2- No i18n provider to wrap the app, only using HOC from next-i18next to wrap _app.
3- I have 2 json files for locales.
Is there a way to allow the useTranslation hook to work and get the parsed value from the translation file? here's what I tried so far too:
1- mocking the useTranslation hook, but this returns the ("another.key") as is without the parsed value.
2- I tried to create a wrapper with i18n-next provider, but that didn't work too.
Here's my test file.
describe("useCustomHook()", () => {
it("Should return correctly mapped props", () => {
const { result } = renderHook(() =>
useCustomHook(mockData)
);
const data = result.current[0];
expect(data.field).toBe(mockData.field); // this returns ("some.key.from.json.file") it doesn't use the t function,
// ... //
});

Vue test utils: extend VueWrapper to add plugin method

I am using Vue test utils and Typescript. I have added Data Test ID Plugin.
How can I extend VueWrapper interface to avoid this error:
Property 'findByTestId' does not exist on type 'VueWrapper<{ $: ComponentInternalInstance; $data: { showUserMenu: boolean ...
One solution is to export a type that adds findByTestId:
// my-vue-test-utils-plugin.ts
import { config, DOMWrapper, createWrapperError, type VueWrapper } from '#vue/test-utils'
👇
export type TestWrapper = VueWrapper<any> & {
findByTestId: (selector: string) => DOMWrapper<HTMLElement>
}
const DataTestIdPlugin = (wrapper: VueWrapper<any>) => {
function findByTestId(selector: string) {
const dataSelector = `[data-testid='${selector}']`
const element = wrapper.element.querySelector(dataSelector)
if (element) {
return new DOMWrapper(element)
}
return createWrapperError('DOMWrapper')
}
return {
findByTestId
}
}
config.plugins.VueWrapper.install(DataTestIdPlugin as any)
Then, use type assertion (as keyword followed by the exported type above) on the mount() result:
// MyComponent.spec.ts
import type { TestWrapper } from './my-vue-test-utils-plugin.ts'
describe('MyComponent', () => {
it('renders properly', () => { 👇
const wrapper = mount(MyComponent) as TestWrapper
expect(wrapper.findByTestId('my-component').text()).toBe('Hello World')
})
})
Another option is to create a .d.ts file e.g. vue-test-utils.d.ts with the following content:
import { DOMWrapper } from '#vue/test-utils';
declare module '#vue/test-utils' {
export class VueWrapper {
findByTestId(selector: string): DOMWrapper[];
}
}
So you're able to extend the existing definition of the VueWrapper class.

How to use react-i18next inside BASIC function (not component)?

I know that react-i18next work in every component: functional (with useTranslation) and class component (with withTranslation()) BUT I can't use translation inside a basic function like this:
const not_a_component = () => {
const { t } = useTranslation();
return t('translation')
};
const translate = not_a_component();
ERROR HOOKS !
Thanks !
You could just use i18next library for translation using javascript.
react-i18next is just a wrapper library on top of i18next.
Below is an example if you are already using react-i18next and it is configured.
import i18next from "i18next";
const not_a_component = () => {
const result = i18next.t("key");
console.log(result);
return result;
};
export default not_a_component;
If you opt to use only i18nextthen you could simply get t function.
It all depends upon your requirement.
import i18next from 'i18next';
i18next.init({
lng: 'en',
debug: true,
resources: {
en: {
translation: {
"key": "hello world"
}
}
}
}, function(err, t) {
// You get the `t` function here.
document.getElementById('output').innerHTML = i18next.t('key');
});
Hope that helps!!!
Alternatively, you can pass t as an additional parameter:
const not_a_component = (t) => {
return t('translation')
};
// Within a component
const { t } = useTranslation()
not_a_component(t)

Async function support in Mithril.js for webpack dynamic imports?

I'm trying to figure out how to use dynamic import in webpack with mithril. To do that elegantly, I think I'll need to use an async function somewhere along the line. Right now this is how I have used the async function:
import m from 'mithril'
let App = async () => {
let { Component } = await import('./components.js')
return {
view () {
return m(Component)
}
}
}
App().then(app => m.mount(document.body, app))
Ideally, I want to use it like this:
import m from 'mithril'
let App = {
async view () {
let { Component } = await import('./components.js')
return m(Component)
}
}
}
m.mount(document.body, App)
Is there something I've been missing from the documentation to acheive what I'd like to do? I've tried to look at every mention of promise, but it's possible that I've missed this.
Any help would be appreciated.
One way that should work is this:
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
(async () => {
const myModule = await import('./myModule.js');
})();
For further information follow the link below.
ES proposal: import() – dynamically importing ES modules
Try the following, which provides a simple component named DynamicComponent which can be used anywhere and with children:
App.js
import m from 'mithril'
import { DynamicComponent } from './DynamicComponent'
const App = {
view() {
return m( DynamicComponent, {
component: 'OtherComponent'
}, 'Hello world' ),
}
}
}
m.mount(document.body, App)
OtherComponent.js
import m from 'mithril'
export function OtherComponent() { return {
view({ children }) { return m( 'div', children )}
}}
DynamicComponent.js
import { hooks } from '/hooks'
export function DynamicComponent() { return {
...hooks,
attrs: null,
component: null,
view({ children }) { return (
// Await module resolution and process attributes.
// Use '&&' as a shortcut to only continue
// once 'this.component' isn't null.
// Pass a clone of attrs to the loaded component.
this.component && m( this.component.default, this.attrs, children )
)}
}}
hooks.js
async function oninit({ attrs }) {
// Preload -> Load immediately, in parallel
// Prefetch -> Load when browser is idle (Can be less responsive)
// See more: https://webpack.js.org/guides/code-splitting/#prefetching-preloading-modules
// Dynamically import component and append '.js' (Don't include '.js' in your imports).
if ( attrs.loadType = 'prefetch' ) {
// Lazy load
this.component = await import( /* webpackPrefetch: true */ `
${ attrs.component }.js`
)
} else {
// Immediate load
this.component = await import( /* webpackPreload: true */ `
${ attrs.component }.js`
)
}
/*
Process and pass along attributes
This clones the attributes to prevent any changes from affecting
the original attributes.
You can save memory if it becomes a problem by directly
assigning `v.attrs` to `newAttrs`, but you lose this immutability.
*/
const newAttrs = { ...v.attrs }
// Remove attributes used in `DynamicComponent`
delete newAttrs.component
delete newAttrs.loadType
// Assign to component
this.attrs = newAttrs
m.redraw()
}
export const hooks = {
oninit,
}

Aurelia Validation with i18n?

Has anyone gotten Aurelia Validation to work with the i18n Plugin for multi-lingual error messages? My app won't even start when I add in the code from the Aurelia documentation http://aurelia.io/hub.html#/doc/article/aurelia/validation/latest/validation-basics/12.
Here's my main.js:
import environment from './environment';
import {I18N} from 'aurelia-i18n';
import XHR from 'i18next-xhr-backend';
import {ValidationMessageProvider} from 'aurelia-validation';
//Configure Bluebird Promises.
//Note: You may want to use environment-specific configuration.
Promise.config({
warnings: {
wForgottenReturn: false
}
});
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.feature('resources')
.plugin('aurelia-validation');
aurelia.use.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(XHR);
// adapt options to your needs (see http://i18next.com/docs/options/)
instance.setup({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
lng : 'en',
ns: ['translation'],
defaultNS: 'translation',
attributes : ['t','i18n'],
fallbackLng : 'en',
debug : false
});
});
// Straight from Aurelia Documentation
const i18n = aurelia.container.get(i18n);
ValidationMessageProvider.prototype.getMessage = function(key) {
const translation = i18n.tr(`errorMessages.${key}`);
return this.parser.parseMessage(translation);
};
// Straight from Aurelia Documentation
ValidationMessageProvider.prototype.getDisplayName = function(propertyName) {
return i18n.tr(propertyName);
};
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin('aurelia-testing');
}
aurelia.start().then(() => aurelia.setRoot());
}
The error I get is vendor-bundle.js:3394 Error: key/value cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI?(…)
If I delete the two sections marked // Straight from Aurelia Documentation, it works fine (but only in one language).
If you see an error in my code, please point it out. Or, if you have a working example using aurelia-validation and aurelia-i18n working together, please pass on a link. Thanks!
Ran into this issue as well. It appears that the line
// Straight from Aurelia Documentation
const i18n = aurelia.container.get(i18n);
is getting (or more likely creating) a different instance of i18n than the
aurelia.use.plugin('aurelia-i18n', (instance) =>
I fixed this by getting the i18n instance directly from the aurelia.use.plugin() as follows (this is typescript but same principle applies to pure js):
let i18n:I18N = null;
aurelia.use.plugin('aurelia-i18n', (instance:I18N) => {
i18n = instance;
//rest of plugin code here
}
Use the imported I18N instead:
const i18n = aurelia.container.get(I18N);
But indeed, i18n seems to stop working afterward. My solution was to update the i18n singleton instance in the first page (app.js), the first time it gets injected:
constructor(i18n) {
this.i18n = i18n;
this.initAureliaSingletons();
}
/**
* Some configurations breaks in 'main.js'
* singletons can be configure here
* #return {void}
*/
initAureliaSingletons() {
const i18n = this.i18n;
ValidationMessageProvider.prototype.getMessage = function(key) {
const translation = i18n.tr(`validation-${key}`);
return this.parser.parseMessage(translation);
};
}
I put it on my main and it works. I think that the trick was to use the variable that was initialized in the plug-in initialization:
var i18n;
aurelia.use.plugin('aurelia-i18n', (instance) => {
// register backend plugin
instance.i18next.use(Backend.with(aurelia.loader)).use(LngDetector);
i18n = instance;
(...)
aurelia.use.plugin('aurelia-validation');
var standardGetMessage = ValidationMessageProvider.prototype.getMessage;
ValidationMessageProvider.prototype.getMessage = function (key) {
if (i18n.i18next.exists(key)) {
const translation = i18n.tr(key);
return this.parser.parse(translation);
} else {
return standardGetMessage(key);
}
};
ValidationMessageProvider.prototype.getDisplayName = function (propertyName, displayName) {
if (displayName !== null && displayName !== undefined) {
return displayName;
}
return i18n.tr(propertyName);
};