Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

returnObjects: true returns keys rather than the translation #240

Closed
mjmasn opened this issue Mar 23, 2017 · 6 comments
Closed

returnObjects: true returns keys rather than the translation #240

mjmasn opened this issue Mar 23, 2017 · 6 comments

Comments

@mjmasn
Copy link

mjmasn commented Mar 23, 2017

I will preface this by saying I'm probably doing this wrong with regards to the translate function - I have been wrapping my components as follows:

export default translate([])(SomeComponent);

Not providing any namespaces works fine for any plain namespace and translation. When using returnObjects: true it stops working and just returns an object or array of keys.

I get that I'm supposed to be specifying namespaces in translate(['ns1','ns2']) but is there a technical reason it not to work without, given that it does work for returning strings. Would just like to avoid having to specify 6 or 7 namespaces every time I want an object in a component.

For clarification:
ns1.json

{
    "stringValue": "Some value",
    "myArray": ["a","b","c"]
}

index.js

// ... 

const resources = {
    ns1: require('./ns1.json')
    // ns2: ...
};

i18next.init({
    lng: 'en',
    fallbackLng: 'en',
    resources
}, (err, t) => { 
    // ReactDOMServer.renderToStaticMarkup(...)
})

component.js

import _ from 'lodash';
import React from 'react';
import { translate } from 'react-i18next';

const SomeComponent = function (props) {
    const { t } = props;

    return <div>
        <div>
            {_.map(t('ns1:myArray', { returnObjects: true }), (item) => {
                return <p>{item}</p>
            })}
        </div>
    </div>
}

SomeComponent.propTypes = { 
    t: React.PropTypes.func.isRequired
};

export default translate([])(SomeComponent);

Gives the following html:

<div>
    <div>
        <p>myArray.0</p>
        <p>myArray.1</p>
        <p>myArray.2</p>
    </div>
</div>
@mjmasn
Copy link
Author

mjmasn commented Mar 23, 2017

Ok so replacing t(...) with i18n.t(...) works here. Guess the issue is in the use of i18n.getFixedT although given how it works (i.e. specifying ns array) I'm not sure you'll want to allow any namespace to work with that.

Setting the fallbackNS option also works in that I could just put all my namespaces in there but I don't think I can use that here because I have multiple namespaces with same keys (e.g. app1:x, app2:x, app3:x, common:x) and wouldn't want to fallback across those namespaces (other than falling back to common if the key doesn't exist for a particular app). Maybe putting common first would work if order is preserved when falling back but then only if I could guarantee that all keys that exist in more than one namespace also exist in the common namespace...

This can probably be closed unless you can think of a different workaround than using i18n.t. I'm happy with that option anyway.

@jamuhl
Copy link
Member

jamuhl commented Mar 23, 2017

a) not giving a namespace in translate HOC will result in defaulting to defaultNS passed in i18next.init (defaults to "translation"). Like you write in the sample you can still use t('ns1:myArray') to access different namespace (specifing them in array will just assert that they get loaded if using a backend plugin and sets first as default)

b) passing resources on init - is not the best idea - you will need to include all languages you provide (use the xhr-plugin to load from your server or http:https://locize.com as a backend)

=> pass it like:

const resources = {
   en: {
     ns1: require('./ns1.json')
     // ns2: ...
  }
};

i18next.init({
    lng: 'en',
    fallbackLng: 'en',
    resources
}, (err, t) => { 
    // ReactDOMServer.renderToStaticMarkup(...)
})

else it will find nothing and fallback to keys (or fallbackNS)

honestly i'm wondering why your sample iterates over the array even if passed without lng.

for acting different when using t vs i18n.t directly...will need to test that...should have same behaviour...will fix that asap if different

@mjmasn
Copy link
Author

mjmasn commented Mar 23, 2017

Thanks for the reply and sorry for the confusion - my example is wrong - in fact my real code is as you describe (keyed by language not by namespace), I was just writing from memory.

I should probably explain this is server side, for email rendering with ReactDOMServer, so I just load all required languages and namespaces from JSON files (it's all bundled into a single js file with webpack so saves time loading it later).

It's a problem specific to returnObjects which I think is down to how getFixedT works but I can't quite work it out...

Looking at this code https://github.com/i18next/i18next/blob/118e47d569650b3c6d81b9afe24c5c4b01d051fb/src/i18next.js#L221-L231

It seems that lng === null and ns === [] whenever t is called inside a React component (if translate was called with []) - because t is just i18n.getFixedT(null, namespaces);

So when the underlying i18n.t is called the params are something like 'ns:key', { lng: null, ns: [], returnObjects: true } - and it seems that the ns: [] is the problem.

I guess one solution is to add the namespace to the ns array when it is extracted: https://github.com/i18next/i18next/blob/118e47d569650b3c6d81b9afe24c5c4b01d051fb/src/Translator.js#L65

At L65 we have the correct namespace but it isn't passed to this.resolve because options.ns is still []. I guess adding options.ns = [namespace] on L66 could work?

Hope that just about makes sense! What I can't quite work out is why returning a plain string works but using returnObjects fails.

i18next.t('app1:blah.someString', { lng: null, ns: [] }); // works (returns 'This is some string')
i18next.t('app1:blah.someArray', { lng: null, ns: [], returnObjects: true }); // fails (returns ['app1:blah.someArray0', 'app1:blah.someArray1', ...]

tl;dr

Was about to post the reply but then realised the issue is here: https://github.com/i18next/i18next/blob/118e47d569650b3c6d81b9afe24c5c4b01d051fb/src/Translator.js#L99

options.ns overwrites namespaces

This should work I think (assuming joinArrays should always be false here):

copy[m] = this.translate(`${key}${keySeparator}${m}`, {...options, ...{joinArrays: false, ns: namespaces}});

@jamuhl
Copy link
Member

jamuhl commented Mar 23, 2017

think yes...should be correct.

applied your fix to [email protected]

@mjmasn
Copy link
Author

mjmasn commented Mar 23, 2017

Working perfectly now, thanks @jamuhl :)

@mjmasn mjmasn closed this as completed Mar 23, 2017
@jamuhl
Copy link
Member

jamuhl commented Mar 23, 2017

thanks belongs to you - for finding and fixing...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants