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

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)

Related

Trying to change change page title, using Quasar, meta and vue-i18n

I am using Quasar v2, using the Vue Composition API and vue-i18n, and I would like the site title to change display when the active language changes (via a drop down), but whatever I am trying does not result in the title language being changed. Any ideas?
Below is what I have right now (just the essentials):
import { defineComponent, ref, computed } from 'vue';
import { useMeta } from 'quasar';
export default defineComponent({
setup () {
const { t: translate } = useI18n() as any;
const siteTitle = computed(() => translate('title.app') as string);
const pageMetadata = {
title: 'untitled',
titleTemplate: (title: string) => `${title} - ${siteTitle.value}`
};
useMeta(pageMetadata);
}
});
The code I am using to switch languages:
async onChangeLanguage () {
try {
let locale = this.language;
if (this.language === 'en') {
locale = 'en-GB';
}
this.$i18n.locale = locale;
const quasarLang = await import(`quasar/lang/${locale}`);
if (quasarLang) {
Quasar.lang.set(quasarLang.default);
}
} catch (error) {
this.$log.error(error);
}
}
According to the documentation, useMeta will not be reactive if you pass a simple object to it. Rather, you should pass a function that returns the desired value:
export default defineComponent({
setup () {
const { t: translate } = useI18n() as any;
const siteTitle = computed(() => translate('title.app') as string);
useMeta(() => {
const title = 'untitled';
const titleTemplate = `${title} - ${siteTitle.value}`
return { title, titleTemplate }
});
});

How to use a postProcess with i18next?

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"]
})
}

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,
}

How to use a mocked data with react-apollo for tests

I'm using react-apollo to build a client that consumes a GraphQL API, however, I'm very stuck on testing. What I want is to mock the server so I can easily test the application without needing to make network calls.
I've found some pointers on how to mock the server:
https://dev-blog.apollodata.com/mocking-your-server-with-just-one-line-of-code-692feda6e9cd
http://dev.apollodata.com/tools/graphql-tools/mocking.html#addMockFunctionsToSchema
But there isn't really an example on how to use this mocked server in my app tests to avoid hitting the server.
My goal is to setup integration tests to assert that the app is actually working:
describe('Profile feature', () => {
beforeAll(() => {
store = setupStore();
app = mount(
<ApolloProvider store={store} client={apolloClient}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</ApolloProvider>
);
});
});
The store is using Redux and the client is being created like this:
const networkInterface = createNetworkInterface({
uri: process.env.REACT_APP_API_URL
});
export const apolloClient = new ApolloClient({
networkInterface
});
How can I use a mocked server with graphql-tools here instead of the actual API?
I found 2 different ways of creating mocked data for apollo-client queries:
The first is to use graphql-tools to create a mocked server based on your backend schema, in order to connect this mocked server with your tests it's possible to create a mockNetworkInterface like this:
const { mockServer } = require("graphql-tools");
const { print } = require("graphql/language/printer");
class MockNetworkInterface {
constructor(schema, mocks = {}) {
if (schema === undefined) {
throw new Error('Cannot create Mock Api without specifying a schema');
}
this.mockServer = mockServer(schema, mocks);
}
query(request) {
return this.mockServer.query(print(request.query), request.variables);
}
}
You can pass this network interface to the ApolloClient component and it should work just fine!
Having this setup requires to have your API schema up to date in your client, so I found it a bit of a pain to do.
Another way of doing this is using the mockNetworkInterface provided by apollo-client/test-utils
You can use it this way:
import App from './App';
import { UserMock, PublicationMock } from '../__mocks__/data';
import { mockNetworkInterface } from 'react-apollo/test-utils';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
// We will be using here the exact same Query defined in our components
// We will provide a custom result or a custom error
const GraphQLMocks = [
{
request: {
query: UserProfileQuery,
variables: {}
},
result: {
data: {
current_user: UserMock
}
}
}
];
// To set it up we pass the mocks to the mockNetworkInterface
const setupTests = () => {
const networkInterface = mockNetworkInterface.apply(null, GraphQLMocks);
const client = new ApolloClient({ networkInterface, addTypename: false });
const wrapper = mount(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
return {
store,
wrapper
};
};
// Then the tests look like this
describe('Profile feature', () => {
test('Profile view should render User details', async () => {
const { wrapper, store } = setupTests();
const waitFor = createWaitForElement('.profile');
await waitFor(wrapper);
const tag = wrapper.find('.profile-username');
expect(tag.text()).toEqual(`${UserMock.first_name} ${UserMock.last_name}`);
});
});
It is important to pass addTypename: false to the ApolloClient instance, otherwise you will need to add __typename to all your queries manually.
You can inspect the implementation of the mockNetworkInterface here: https://github.com/apollographql/apollo-test-utils/blob/master/src/mocks/mockNetworkInterface.ts
You can also use MockedProvider, which makes it even simpler.
withPersons.js
import { gql, graphql } from 'react-apollo'
export const PERSONS_QUERY = gql`
query personsQuery {
persons {
name
city
}
}
`
export const withPersons = graphql(PERSONS_QUERY)
withPersons.test.js
/* eslint-disable react/prop-types */
import React, { Component } from 'react'
import { MockedProvider } from 'react-apollo/test-utils'
import { withPersons, PERSONS_QUERY } from '../withPersons'
it('withPersons', (done) => {
const mockedData = {
persons: [
{
name: 'John',
city: 'Liverpool',
},
{
name: 'Frank',
city: 'San Diego',
},
],
}
const variables = { cache: false }
class Dummy extends Component {
componentDidMount() {
const { loading, persons } = this.props.data
expect(loading).toBe(true)
expect(persons).toBe(undefined)
}
componentWillReceiveProps(nextProps) {
const { loading, persons } = nextProps.data
expect(loading).toBe(false)
expect(persons).toEqual(mockedData.persons)
done()
}
render() {
return null
}
}
const DummyWithPersons = withPersons(Dummy)
mount(
<MockedProvider
removeTypename
mocks={[
{
request: { query: PERSONS_QUERY, variables },
result: { data: mockedData } },
]}
>
<DummyWithPersons />
</MockedProvider>,
)
})
Note: By using a Dummy component you just test your graphql() Queries and Mutations and the way you have configured them (options, props, skip, variables, etc.) So you don't mount your actual React components. It's better to test those in their 'unconnected' state.
I wrote up a blog post a while that might be helpful: http://blog.dideric.is/2018/03/18/Testing-apollo-containers/
Apollo has something called LinkSchema that makes the first approach Carlos mentioned a lot easier. It still takes some setup, but I think it's worth it. If you're creating responses manually, you have to worry a lot more about keeping your tests up to date/getting false positives when the schema changes and you haven't accounted for it in your code.

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);
};