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

localeStructure without sub-directory keeps searching for subdirectory #1500

Closed
bartrail opened this issue Nov 4, 2021 · 15 comments
Closed

Comments

@bartrail
Copy link

bartrail commented Nov 4, 2021

localeStructure without sub-directory keeps searching for subdirectory

When changing the configuration to use a flat directory structure like this, so I can use files like common.en.json or something-else.fr.json there is an error thrown, expecting the language directory to be there.

configuration:

const path = require('path')

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'de', 'fr'],
  },
  localeStructure: '{{ns}}.{{lng}}',
  localePath: path.resolve('./public/translations'),
}

Error:

Error: ENOENT: no such file or directory, scandir '/Users/con/work/project/app/public/translations/de'
at Object.readdirSync (fs.js:1047:3)
at getLocaleNamespaces (/Users/con/work/project/app/node_modules/next-i18next/dist/commonjs/config/createConfig.js:180:23)
at /Users/con/work/project/app/node_modules/next-i18next/dist/commonjs/config/createConfig.js:188:20
at Array.map (<anonymous>)
  at getNamespaces (/Users/con/work/project/app/node_modules/next-i18next/dist/commonjs/config/createConfig.js:185:44)
  at createConfig (/Users/con/work/project/app/node_modules/next-i18next/dist/commonjs/config/createConfig.js:228:29)
  at _callee$ (/Users/con/work/project/app/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js:199:53)
  at tryCatch (/Users/con/work/project/app/node_modules/regenerator-runtime/runtime.js:63:40)
  at Generator.invoke [as _invoke] (/Users/con/work/project/app/node_modules/regenerator-runtime/runtime.js:294:22)
  at Generator.next (/Users/con/work/project/app/node_modules/regenerator-runtime/runtime.js:119:21) {
    errno: -2,
    syscall: 'scandir',
    path: '/Users/con/work/project/app/public/translations/de'
  }

Occurs in next-i18next version

8.8.0

Steps to reproduce

change the configuration to the one above

Expected behaviour

The loading of the files works as expected.

Additional context

I did some debugging in the createConfig.js from node-modules and found out, that the combinedConfig variable is filled correctly and these variables also look correct:

var defaultLocaleStructure = localeStructure.replace("".concat(prefix, "lng").concat(suffix), lng).replace("".concat(prefix, "ns").concat(suffix), defaultNS);
var defaultFile = "/".concat(defaultLocaleStructure, ".").concat(localeExtension);
var defaultNSPath = path.join(localePath, defaultFile);
var defaultNSExists = fs.existsSync(defaultNSPath);

console.log(defaultLocaleStructure);
console.log(defaultFile);
console.log(defaultNSPath);
console.log(defaultNSExists);

outputs:

common.de
/common.de.json
/Users/con/work/project/app/public/translations/common.de.json
true

So I think the problem relies in some un-configured namespace configuration below where path is concatinated without respecting the combined config.

var namespacesByLocale = locales.map(function (locale) {
return getLocaleNamespaces(path.resolve(process.cwd(), "".concat(serverLocalePath, "/").concat(locale)));
});
@isaachinman
Copy link
Contributor

Sounds like you've debugged it thoroughly. Would definitely accept a PR for this bug fix if you have the time?

@bartrail
Copy link
Author

bartrail commented Nov 4, 2021

I will try to do this in the next couple of days ✌️

@BeckerNico
Copy link

I've created a PR for this Issue.
The basic idea is that there is a check wether the language is part of the folder structure or of the filename. If it is part of the folder structure we continue as always.
But if the language is part of the filename we assume that files of multiple languages were placed in one folder. Then we get all files of that folder and remove the language from the filename to get unified namespaces. The language is removed with a regex that will only match if the language is not part of a word. This prevents that a filename like default.de.json will be fault and not default. I have also written some tests for that regex and for the new structure.
If i unterstood Issue #1116 correct, this schould also be fixed with this PR.
Let me know if anything is missing :)

@isaachinman
Copy link
Contributor

@BeckerNico Much appreciated! However, I don't think this should be solved via a regex. The solution should be declarative via the templating syntax.

@BeckerNico
Copy link

BeckerNico commented Nov 23, 2021

@isaachinman I tried that, but the problem is, that without the regex i'm not able to remove the special chraracters around the language. For example if the filename is common.de and we are just replacing the {{lng}} part we get the namespace common. and not common . This even gets more complicated when the filename is something like translation.en.common or translation-en-common . In best case it should support every position of the language and every special character. I will try to figure out a solution, but maybe you have any idea how we could achieve that?

@isaachinman
Copy link
Contributor

Perhaps check the i18next ecosystem for inspiration, as this is where the templated path option comes from.

@BeckerNico
Copy link

@isaachinman i adjusted the PR by removing the regex and using only the templating syntax to extract the namespace from the filename. What do you think?

@bartrail
Copy link
Author

thx @BeckerNico for taking care of this ✌️

@bartrail
Copy link
Author

@isaachinman did you have time to look into the updated PR?

@isaachinman
Copy link
Contributor

isaachinman commented Dec 15, 2021

@bartrail @BeckerNico Here are my thoughts:

Based on comments like #1202 (comment):

I would recommend against allowing a custom localePath as this makes it not able to be statically analyzable and it will not be able to detect the files being needed automatically.

I think the most "custom" fs support we'd proceed with in next-i18next would be to allow the user to provide a custom function to resolve files/namespaces, instead of trying to predict and support all possible permutations.

I am envisioning a future state where next-i18next has a rigid expectation of ./public/locales/{{locale}}/{{namespace}}.{{ext}}, and if you want to use anything else, you will need to provide your own resolution function.

I am very hesitant to merge a PR like #1546, as it will just lead to further and further edge cases popping up, as the possibilities are literally infinite.

@lucaslcode
Copy link
Contributor

Hi @isaachinman, I am thinking to make this PR as you said, since Couchers is transitioning to NextJS and we need this functionality.

I was thinking to keep localeStructure/Extension for backwards compatibility, and overload loadPath to take a function as with other i18next backends, in which case localStructure/Extension would be ignored. How does that sound?

@isaachinman
Copy link
Contributor

@lucaslcode I think we'd be better off introducing a new/dedicated config property, rather than overriding any i18next props.

@lucaslcode
Copy link
Contributor

@isaachinman my bad sorry - I meant override localePath, not loadPath

@lucaslcode
Copy link
Contributor

@isaachinman this is coming along. I just have a question about the workings that I can't quite figure out.

You're using i18-fs-backend as the backend by default. This only works on node afaik, yet you're creating a separate config for the client which strips /public from the path to the translation files. Does the client actually use the files or is there some other reason for this? Does that mean the resources by necessity need to be in the public directory in order to be reachable from the client?

Thanks so much!

@isaachinman
Copy link
Contributor

Does that mean the resources by necessity need to be in the public directory in order to be reachable from the client?

If you want it to work out of the box, yes. The docs are here.

Let me know if I can answer any other questions, and a massive thanks for digging into this!

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

Successfully merging a pull request may close this issue.

4 participants