gilbsgilbs / babel-plugin-i18next-extract

Babel plugin that statically extracts i18next and react-i18next translation keys.
https://i18next-extract.netlify.com
MIT License
161 stars 37 forks source link

Support for combining JSON locale files (?) #93

Closed Jacob-Finn closed 4 years ago

Jacob-Finn commented 5 years ago

This might already be a feature and I just haven't been able to figure out how to get it work successfully, but currently in my project I am storing keys inside of JSON files, for example login.JSON might contain:

{ "login": { "placeHolder": "Phone number", "login": "Login" } }

while my common.JSON might contain: { "common": { "welcome": "Welcome to the Home Menu", "language": "You are now using English", "date": "The current date is {{date, 'LLLL'}}" } }

I basically want to be able to combine these into a single JSON file with their namespaces and keys still intact, but whenever I try to have i18next-extract target JSON files, I usually get an error on the colon ':' where a semicolon is expected ';'.

gilbsgilbs commented 5 years ago

Hi,

I'm not sure to understand why you would do such thing, but wouldn't setting outputPath to extractedTranslations/{{locale}}.json be what you're looking for? Not sure it would work as expected, but it's worth a try I think.

If you encounter some crash, please provide reproduction steps.

Jacob-Finn commented 5 years ago

@gilbsgilbs Sorry for the very long delay in responding, I haven't had a chance to come back to this issue till today, As for why I would want to do such a thing, I use a ducks structure format, where my components and screens are separated. So my file format looks something like this

So as you can see, I have a lot of JSON files containing keys in lots of different places, this makes it easier to work on a feature/screen at one time without going through a giant JSON file for translations. Why I need this feature is that I thought this extractor would be able to extract all my keys out of these JSON files in my entire project and organize them neatly all together, that way sending my translation files to an external source to be translated would be as simple as running a command and sending off the JSON file, rather than manually having to make a giant JSON file to send off by going into each component and each screen to copy and paste the keys.

I followed your advice and now it does seem like I am getting a collective JSON file under extractedTranslations/en.json, but whenever I open the JSON file, it doesn't appear like the extractor is finding any of my key's default values that I have kept in the JSON files. { "date": "", "login": "", "placeHolder": "", "welcome": "" }

gilbsgilbs commented 5 years ago

I might be idiotic, but I still don't get the desired workflow. I'm not sure if you'd want this plugin to map translation file path depending on the location of the source file (but how would it know?) or if you'd want to create a "giant JSON file" or both (but how would you combine them?). If by any chance that is of help, specifying values in the giant JSON file is something you'd only have to do once. The plugin should not clear or overwrite values that already exist in that file. But I don't think you wanted to get rid of your JSON splits so I don't know, I don't get it, sorry :man_shrugging: .

In any case, unless I really missed something (which I probably did), that sounds quite convoluted and specific to me. That's probably not something I'm willing to have built-in support for right now (unless somebody can come up with an elegant and versatile solution). The way I'd like to address complex workflows like this would be by exposing a plain JS API that would let you hook into the exporter to export what you want where you want (using actual JS code rather than configuration). But that's not done yet.

Might be very similar to https://github.com/gilbsgilbs/babel-plugin-i18next-extract/issues/76.

Jacob-Finn commented 5 years ago

@gilbsgilbs I was actually able to solve this issue by creating my own importer/exporter using Node. Sorry for all the confusion! I will leave this issue open incase you want to keep it for some reason, but otherwise I'd be fine with closing this issue.

gilbsgilbs commented 5 years ago

In fact, I'd like to understand how people use i18next. Sure this plugin could expose some JS hooks that would do the trick, but I believe there could be pragmatic solutions that could solve legitimate and common workflows (like the one described in #76 and maybe in this issue) without the need to write any code.

In particular, being able to specify an object as output path could be of help in some cases:

{
  "outputPath": {
    "./src/Features/Login/LoginForm/**/*.{js,jsx}": "./src/Features/Login/LoginForm/locales/{{ns}}/{{locale}}.json",
    "./src/Screens/Login/**/*.{js,jsx}": "./src/Screens/Login/locales/{{ns}}/{{locale}}.json"
  }
}

I don't think this would be very bad maintenance-wise. I'm just unsure whether it would really be used.

Jacob-Finn commented 5 years ago

@gilbsgilbs I can't currently share my project that I'm working on as it isn't allowed to be released to the public, but I'll try and put together a rough draft sometime later today to show you what I mean, as this is something that I feel it is easier to show than tell what I'm talking about.

acidoxee commented 5 years ago

Hi @Jacob-Finn, I could be interested in the solution you found or built yourself to solve your problem. I'd also like to split my locales into several files across my projets (for instance, let's say one file per page and per locale), but I can't quite figure it out. Any help you can spare would be appreciated 👍

Jacob-Finn commented 5 years ago

@acidoxee @gilbsgilbs My solution: https://pastebin.com/L4xcQgTc (requires nodes). Basically it will search through all folders and subfolders under ../../features and ../../screens and then store all the JSON keys it finds into a masterTranslation.json folder, this can then be sent to a translation service containing all your keys for your project. The lines that you would need to change per project are lines 5, 6, and 25. To run the script I just added a command to my package.json: "extract-translation-keys": "cd src && cd utils && cd i18next && node generateLocaleMaster & cd ../../../" Hopefully that is clear enough as to what is happening, if you need anymore help feel free to tag me again @acidoxee.

acidoxee commented 5 years ago

Thanks for the quick reply @Jacob-Finn. Your script does seem interesting and would solve some of our problems.

Unfortunately, I don't think your solution can be integrated with existing key extraction tooling such as this plugin, since those tools themselves would need to be able to extract missing keys to the proper split locale files, which (correct me if I'm wrong) is not the case.

I guess the only case where this wouldn't be a problem is if you split all your locales per component, since the missing keys in the component could immediately be identified by looking into the adjacent locale files, but boy would that be a PITA to manage in the long run 😄

Since manually extracting missing keys and not having automatic linting would be a bigger pain for us than not having several-folders-splitting functionality, for now we'll stick to this Babel plugin, which at least does a great job at... well... extracting keys from everywhere :)

Thanks anyway for your help!

gilbsgilbs commented 5 years ago

@acidoxee would something like this suggestion solve your issue?

acidoxee commented 5 years ago

Yes actually, that would probably be a first step towards the solution we're looking for. My bad for having overlooked that answer 👍

I'm just wondering if having to maintain the correspondance by hand could ever become a burden. A simpler binding that wouldn't require any maintenance would maybe be better in the long run. For instance, by using the namespace to bind to a page folder (with the exact same name obviously) like so:

{
  "outputPath": "src/pages/{{ns}}/__locales__/{{locale}}.json"
}

This is already possible today with your plugin. But it doesn't account for special namespaces that don't have anything to do with pages, such as common, errors and similar stuff, which we'd like to keep elsewhere. In that regard, a JS hook could solve the problem by allowing us to customize the output path with a function based on the namespace, locale and file path of each key found by your plugin.

Then, the re-packaging of all those split locales into a single file per locale could be handled with @Jacob-Finn's solution, that could be triggered in CI/CD just before the final build. Does this workflow look legit?

gilbsgilbs commented 5 years ago

JS hooks are definitely part of the plan but it may take some time to build a good and stable API. I want to address most frequent use-cases without requiring the user to write JS code and I also want to fix some of the remaining bugs before opening the hook API. Therefore it might take a while until we get there.

It seems that splits are quite demanded for both monorepos setups (which are very common) and per-folder translations, so it makes sense to have a dedicated feature for this IMO.

I'll not be able to work on this very soon, but if somebody wants to contribute, I'll look into it!

gilbsgilbs commented 4 years ago

Closing this in favor of #76.