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

Support returning the language a translation resolves to #16

Closed
nfantone opened this issue Jan 3, 2022 · 5 comments
Closed

Support returning the language a translation resolves to #16

nfantone opened this issue Jan 3, 2022 · 5 comments

Comments

@nfantone
Copy link

nfantone commented Jan 3, 2022

To start with: great little library 👏🏼.

One minor thing I would love to see is a way of knowing which language your returned translation is actually in. This is very useful in case of missing translations.

// i18n.js
import rosetta from 'rosetta'

const i18n = rosetta({ en: {...}, es: {...} })
i18n.locale('en')

export default i18n
// ...elsewhere
import i18n from './i18n.js'

const text = i18n.t('foo.bar', {}, 'it') // <--- No way of knowing, at this point, what language `text` is in

Another way of looking at this is exposing whether or not rosetta managed to find a matching property in the dictionary.

One common use case for this is setting a correct <html language="{{lang}}"> attribute value in an HTML template.

@nfantone nfantone changed the title Support returning the language the translation resolved to Support returning the language a translation resolves to Jan 3, 2022
@lukeed
Copy link
Owner

lukeed commented Jan 3, 2022

Hey, thanks~!

In your example, text would be in "en" because that was your default language set. If you call .locale() again without any arguments, it will always return your language default: https://github.com/lukeed/rosetta#rosettalocalelang

The reason for a "default" at all is that you're able to change the desired language per-t() call by setting a 3rd argument. Here's the relevant slice from the README example:

// Change default language key
i18n.locale('pt');

// Retrieve translations w/ new defaults
i18n.t('intro.text', data); //=> 'Espero que você ache isso útil.'
i18n.t('intro.text', data, 'en'); //=> 'I hope you find this useful.'

Rosetta does not auto-detect your users' languages for you. This is up to you and your application and should be tracked within your own application state. There are some recipes included in #1 (comment) for detecting/getting a user language.

Separately, rosetta offers a rosetta/debug submodule that you an import from during development which will log an error for any undefined/unknown language+token combination. See Debugging for more info.

Hope that all helps~

@lukeed lukeed closed this as completed Jan 3, 2022
@nfantone
Copy link
Author

nfantone commented Jan 3, 2022

Hi @lukeed! Many thanks for your reply.

I'm fully aware of how rosetta works, I've used it extensively. I think you might have misunderstood my question.

In your example, text would be in "en" because that was your default language set.

Yes, that's obvious from reading the modules logic. The real issue here is that there's no built-in way of knowing that programatically. A consumer of the i18n object returned from the rosetta constructor is completely oblivious to the internal contents of the translations dictionary.

The reason for a "default" at all is that you're able to change the desired language per-t() call by setting a 3rd argument.

Exactly. This is demonstrated in the example from my OT. And the reason why I think rosetta is missing a key feature.

Rosetta does not auto-detect your users' languages for you. This is up to you and your application and should be tracked within your own application state.

My concerns have little to do with auto-detecting a language. In my snippets I'm providing a known, hardcoded language. The problem still persists.

Separately, rosetta offers a rosetta/debug submodule that you an import from during development which will log an error for any undefined/unknown language+token combination.

This brings us closer to filling in the gap, but unfortunately that's not an actual solution as I'm sure you'd agree.

Could you please consider re-opening this? I know you didn't ask for it, but as a humble piece of feedback to you, it might be better if you could wait for users responses before deciding whether or not an issue should be closed :)

@lukeed
Copy link
Owner

lukeed commented Jan 3, 2022

If I understood correctly, you’re asking for this:

i18n.t(“foo”, data);
//~> { text: string | void, lang: string }

which would also mean this:

i18n.t(“foo”, data, “en”);
//~> { text: “bar”, lang: “en” }

I’m sorry but this is pointless. You pass in a lang target directly, so echoing the value is of no use when the pooint of the library is to store a dictionary of translations so that you can get them. Accessors must always be known.

And the API is programmatic. And because it’s straightforward it’s easily composable. For example, this little utility does exactly as the above without inducing an unnecessary breaking change:

function t(ctx, token, data, lang) {
  lang = lang || ctx.locale();
  let value = ctx.t(token, data, lang);
  return { lang, value };
}

@nfantone
Copy link
Author

nfantone commented Jan 4, 2022

I think you might still be missing the point, given the examples you're providing. Let me elaborate a little bit.

If I understood correctly, you’re asking for this:

Doesn't have to be that specific API, but sure. That's kind of in the ballpark of what I think we are missing, yes.

I’m sorry but this is pointless.

This is a matter of personal preference, clearly - but I disagree. It's not only not pointless, it's kind of essential to be able to provide accurate results in case of missing locales.

You pass in a lang target directly, so echoing the value is of no use

You would not be echoing the value. That's the whole point of my request. You would be answering with the actual language being returned from the translation.

And the API is programmatic.

Not entirely sure what you meant by this - but I'm gonna put my finger on not having implied otherwise. I said that clients have no programmatic way of knowing the locale that the resulting texts are in. Never mentioned the whole API.

For example, this little utility does exactly as the above without inducing an unnecessary breaking change

It doesn't. And this is the reason why I'm inclined to say I'm doing a bad job at getting through with my questions.

function t(ctx, token, data, lang) {
  lang = lang || ctx.locale(); // <--- This will always return the value of `lang` unless falsy (useless)
  let value = ctx.t(token, data, lang); // <--- Still no way of knowing which language `value` is in
  return { lang, value } //<--- If the passed in `lang` is not among the translations, this result will be wrong
}

Does that make things clearer?

Going back to my original example, your utility would produce the following:

const i18n = rosetta({ en:  {...}, es: {...} })
i18n.locale('en')

// `value` would be either in no language at all (`''`) or EN,
// but `lang` would always be returned as `it`, which is wrong in both cases
const { lang, value } =  t(i18n, 'foo.bar', {}, 'it') 

@lukeed
Copy link
Owner

lukeed commented Jan 4, 2022

Sorry but I don't think you understand how i18n.t works. Here's its source:

t(key, params, lang) {
  var val = dlv(tree[lang || locale], key, '');
  if (typeof val === 'function') return val(params);
  if (typeof val === 'string') return tmpl(val, params);
  return val;
}

If you pass a lang, it is used. That's all that will be used, ignoring the locale fallback (via i18n.locale('<value>'). This means tree[lang || locale] always results in a language object with your translations and then dlv uses that object and your key identifier to find a value, if any. If there's no value, you will see that. If you did not set a lang, then the fallback is used.

In your final snippet, the returned lang will always be "it" because you passed it in. Always. If you did not pass anything in, it will always be the value from i18n.locale(), which is/was your last-defined fallback. If you never set a fallback, then the internal locale remains "" which means you'll never get a translation from i18n.t() unless you explicitly provide a lang value to the t() call.... this be because the internal lookup will evaluate to dlv(tree[undefined || ""], ...) which goes nowhere.

The utility provided does/will do what you've described. If not, then I'm sorry but your expectations are wrong. The lang = lang || ctx.locale() line is exactly the same logic that Rosetta is doing internally on every t() call, so yes, the returned lang value is always going to be correct & align with the value from that response, too. By extension – and by design – that means that you always know (or can always know) exactly what the current language is when asking for a translation.

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