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

Feature Request: support for I18n #175

Open
proddy opened this issue Jul 17, 2020 · 16 comments
Open

Feature Request: support for I18n #175

proddy opened this issue Jul 17, 2020 · 16 comments

Comments

@proddy
Copy link

proddy commented Jul 17, 2020

For one on my projects I'm going to have to translate the Web and Console commands to German and possibly other languages. Is there a quick and easy way to isolate all the string literals in React/TS into a separate language file and decide upon building which one to use?

I had a look at react-intl, i18next-react, polyglot and this one called lingui-js but they'll all quite big libraries and there is just not enough progmem to store all the translations to have it real-time.

@rjwats
Copy link
Owner

rjwats commented Jul 17, 2020 via email

@proddy
Copy link
Author

proddy commented Jul 22, 2020

thanks. I'll add this to my to-do and report back

@rjwats
Copy link
Owner

rjwats commented Jul 22, 2020 via email

@proddy
Copy link
Author

proddy commented Sep 8, 2020

I went for LinguiJS as it's one of the newer libraries, has a nice CLI and much smaller footprint (react-intl was 13Kb, react-il8next 15Kb and lingujs 4kb zipped).

When hard-coding the languages it works fine. I just wrap esp8266-react's root App component in the library's I18nProvider like:

interface/src/index.tsx

  return (
    <Router history={history}>
       <I18nProvider language="de" catalogs={catalogs_de}>
        <App/>
      </I18nProvider>
    </Router>
  );

now I'd like to make it load the catalogs dynamically when a new language is picked. So replacing the line with

<I18nProvider language={language} catalogs={catalogs}>

Problem now is I haven't found any easy way set a global prop that index.tsx would pick up and use. Any pointers on the best way to achieve this? I was thinking of extending App.tsx but thought I'd ask before I start mangling everything and breaking it all.

@rjwats
Copy link
Owner

rjwats commented Sep 8, 2020 via email

@proddy
Copy link
Author

proddy commented Sep 8, 2020

just started, cobbled something together quickly to see how it affected the esp8266's flash mem. It wasn't complex to add as the documentation is good. Extending the feature service sounds like a good idea. My react/TypeScript skills are not as good as yours so I'd probably fudge it. I could create a feature branch in my fork for you to look at, but only changed a few lines

@rjwats
Copy link
Owner

rjwats commented Sep 8, 2020

Internationalization support is very much a worthwhile feature to add. Fortunately the number of strings to internationalize (assuming you ignore the "demo" project) is pretty small too so the bundle shouldn't grow much.

As it happens, one of my work related projects (React) requires internationalization so this will be a useful exercise for me any way. I'm happy to take a look at what you've done so far if you have anything to show - will take a look tonight anyhow and see how it goes.

The goal is clear any way - extensible internationalization with a sensible decision about which library to use.

@rjwats
Copy link
Owner

rjwats commented Sep 8, 2020

LinguiJS is an interesting library. I've not got much to compare it to which is probably the reason I found the workflow to be a little awkward - that's probably par for the course when it comes to i18n though to be fair.

Constructing a const instance of i18n and passing it into the provider seems to give you the most flexibility. I wanted to internationalize the error messages in the JavaScript in the callback code in the SignIn component and this was required in order to do that - as would also be the case for snackbar messages.

Some other notes:

  • The TypeScript types in definitely typed sadly aren’t 100% accurate, language is actually optional if i18n is provided and the types don't declare it as such.
  • Seems like you have decide whether or not to go with message ids or just "source language" strings. I opted for the latter initially - thinking any attempt to come up with a decent scheme for message ids would end in confusion in the long run.

I think it's worth investigating the other popular libraries before settling. I'm concerned that a single set of message bundles ("catalogues") may be awkward conflict-wise if a downstream project wanted to have it's own internationalized UI sections (your use-case in fact) so that might be worth thinking about when evaluating libraries. Bundle size aside, usability is also obviously very important and I just don't know enough about these liraries yet to make a good judgement.

I have a hacky WIP which demonstrates switching languages with a picker in the app menu bar. I've only internalized the SignIn component though so you have to sign out to see the change.

WIP Commit

@proddy
Copy link
Author

proddy commented Sep 9, 2020

wow, that was quick! How you built the LanguageWrapper I would never have figured out myself. I also opted to use po as the language format and followed this guide on how to dynamically compile and load the language files using lingui/loader which I'm hoping saves a little on the Flash mem usage.

(My quick test was in https://github.com/proddy/esp8266-react. I also added the language to sign-in page. I think this is the best place).

@rjwats
Copy link
Owner

rjwats commented Sep 9, 2020

Ah, they provide a HoC to inject the i18n instance into react components. I missed this last night:

https://lingui.js.org/ref/react.html#withi18n

Still, some of our language strings are produced in const scope, that may still require the const instance, take the ntpStatus function here for example:

https://github.com/rjwats/esp8266-react/blob/master/interface/src/ntp/NTPStatus.ts

@rjwats
Copy link
Owner

rjwats commented Sep 9, 2020

My plan is to explore an alternative library tonight to give the evaluation due-diligence.

This looks workable, but need to think about extensiblity for downstream projects still.

@rjwats
Copy link
Owner

rjwats commented Sep 9, 2020

Another quick experiment today, this time with react-intl. Yes, the bundle size is higher, about 25k - so not small at all. I can get this down to a net increase of ~10k though by removing moment which is huge considering what we use from it (really need to do this anyway, moment is insanely large - but we do rely on the UTC offset feature which is not easy to achieve with date-fns IIRC).

On the other hand, this library probably makes for a setup which is easier to document and learn. It certainly seems easier to have multiple message bundles and merge them together (in order keep the "project" messages separate from the "core" messages). I have demonstrated this in the commit below. I haven't implement the switcher, but it's very simliar in the way it works:

Another WIP/Hack

I'm going to continue playing with the js-lingui library to see if I can find a setup which works as well as this one (the separation of message bundles in particular). The smaller minzipped size of that library is a big seller. It seemed it would be more awkward for newcomers to learn being CLI driven (especially on top of the fact they may be new to React and would certainly be new to the framework's idiosyncrasies).

@proddy proddy changed the title Feature Request: support multi-language Feature Request: support for I18n Sep 15, 2020
@proddy
Copy link
Author

proddy commented Aug 20, 2021

I started looking into this again for one of my projects where 1000+ of the users are German speaking and came across another smaller lightweight i18n implementation by Hofer Ivan. It's very easy to set up, has a small footprint, and is written in lovely TypeScript with a good implementation example for React.

The only place where I struggled was around using its functions inside the render()'s because it uses React's useContext hook to pass the data through the app and we're still using component classes and manually passing props down the tree. Which triggered me into why not port everything over to using hooks #251

@rjwats
Copy link
Owner

rjwats commented Aug 20, 2021 via email

@rjwats
Copy link
Owner

rjwats commented Aug 21, 2021 via email

@proddy
Copy link
Author

proddy commented Aug 21, 2021

great! I like the plugin approach too and it's handy to have a npm target that watches both the TS code and re-generates the translations. Together with the proxy/API you can get a lot of coding and testing done without ever having to build and upload the firmware.

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