elixir-cldr / cldr

Elixir implementation of CLDR/ICU
Other
447 stars 33 forks source link

function Cldr.Gettext.Plural.nplurals/1 is undefined or private #148

Closed alaadahmed closed 3 years ago

alaadahmed commented 3 years ago

I was following documentation and trying to add this use Gettext, plural_forms: Cldr.Gettext.Plural to my MyApp.Gettext module, but I got this error

== Compilation error in file lib/ahram_school_web/gettext.ex ==
** (UndefinedFunctionError) function Cldr.Gettext.Plural.nplurals/1 is undefined or private
    (ex_cldr 2.22.1) Cldr.Gettext.Plural.nplurals("ar")
    (gettext 0.18.2) lib/gettext/compiler.ex:587: Gettext.Compiler.warn_if_missing_plural_forms/4
    (gettext 0.18.2) lib/gettext/compiler.ex:539: Gettext.Compiler.compile_translation/7
    (elixir 1.12.1) lib/enum.ex:1553: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.12.1) lib/enum.ex:1553: Enum."-map/2-lists^map/1-0-"/2
    (gettext 0.18.2) lib/gettext/compiler.ex:452: Gettext.Compiler.compile_po_file/4
    (gettext 0.18.2) lib/gettext/compiler.ex:407: Gettext.Compiler.compile_serial_po_file/3
    (elixir 1.12.1) lib/enum.ex:1553: Enum."-map/2-lists^map/1-0-"/2
    (gettext 0.18.2) expanding macro: Gettext.Compiler.__before_compile__/1
    lib/ahram_school_web/gettext.ex:1: AhramSchoolWeb.Gettext (module)
    (elixir 1.12.1) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
kipcole9 commented 3 years ago

Sorry for the inconvenience @alaadahmed. It's a documentation error which I've now fixed to read:

Gettext Backend Pluralization Support

There is an experimental plurals module for Gettext called MyApp.Cldr.Gettext.Plural (where MyApp.Cldr is the name of your backend module). It is configured in Gettext by:

    defmodule MyApp.Gettext do
      use Gettext, otp_app: :my_app, plural_forms: MyApp.Cldr.Gettext.Plural
    end

Basically, in your case, use use Gettext, plural_forms: MyApp.Cldr.Gettext.Plural where MyApp.Cldr is the name of your CLDR backend module.

Please report back if that now works for you - and of course please definitely report back if it does not!

alaadahmed commented 3 years ago

Yes I understand that MyApp.Cldr is my CLDR backend, I did actually what you said but it didn't work either.

Screen Shot 2021-06-06 at 12 46 46
== Compilation error in file lib/ahram_school_web/gettext.ex ==
** (UndefinedFunctionError) function AhramSchoolWeb.Cldr.Gettext.Plural.nplurals/1 is undefined (module AhramSchoolWeb.Cldr.Gettext.Plural is not available)
    AhramSchoolWeb.Cldr.Gettext.Plural.nplurals("ar")
    (gettext 0.18.2) lib/gettext/compiler.ex:587: Gettext.Compiler.warn_if_missing_plural_forms/4
    (gettext 0.18.2) lib/gettext/compiler.ex:539: Gettext.Compiler.compile_translation/7
    (elixir 1.12.1) lib/enum.ex:1553: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.12.1) lib/enum.ex:1553: Enum."-map/2-lists^map/1-0-"/2
    (gettext 0.18.2) lib/gettext/compiler.ex:452: Gettext.Compiler.compile_po_file/4
    (gettext 0.18.2) lib/gettext/compiler.ex:407: Gettext.Compiler.compile_serial_po_file/3
    (elixir 1.12.1) lib/enum.ex:1553: Enum."-map/2-lists^map/1-0-"/2
    (gettext 0.18.2) expanding macro: Gettext.Compiler.__before_compile__/1
    lib/ahram_school_web/gettext.ex:1: AhramSchoolWeb.Gettext (module)
    (elixir 1.12.1) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

and here is my backend config:

defmodule AhramSchoolWeb.Cldr do
  @moduledoc """
  Define a backend module that will host our
  Cldr configuration and public API.

  Most function calls in Cldr will be calls
  to functions on this module.
  """
  use Cldr,
    locales: Gettext.known_locales(AhramSchoolWeb.Gettext),
    default_locale: "ar",
    providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime],
    add_fallback_locales: true,
    gettext: AhramSchoolWeb.Gettext,
    otp_app: :ahram_school,
    data_dir: "./priv/cldr",
    generate_docs: true,
    precompile_transliterations: [{:latn, :arab}, {:arab, :latn}],
    precompile_number_formats: ["#,##0.00 ¤", "¤#,##0.00"],
    force_locale_download: Mix.env() == :prod
end
kipcole9 commented 3 years ago

Thanks for your patience. I've been digging deeper and there is a more fundamental issue I need to resolve relating to compile time dependencies. I'm working on it now but it might take a day or so to finally resolve properly. Will that timing be ok for you?

kipcole9 commented 3 years ago

BTW so happy to see someone working in non-Latin scripts. Please do let me know if you see any other issues!

alaadahmed commented 3 years ago

I was shocked actually with the tremendous amount of efforts you put in these packages, I am the happy one here. Sure take your time, I really appreciate your time and efforts that you will spend or spent already. Thank you and I will report any issue if I found. I read most of documentation today at the morning and it is fascinating.

kipcole9 commented 3 years ago

Still working on this issue. In the meantime, looking at your configuration, I think it can be a bit simpler:

defmodule AhramSchoolWeb.Cldr do
  @moduledoc """
  Define a backend module that will host our
  Cldr configuration and public API.

  Most function calls in Cldr will be calls
  to functions on this module.
  """
  use Cldr,
    # not required, get text locales are merged here anyway
    # locales: Gettext.known_locales(AhramSchoolWeb.Gettext),
    default_locale: "ar",
    providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime],
    add_fallback_locales: true,
    gettext: AhramSchoolWeb.Gettext,
    otp_app: :ahram_school,
    # Not required, this is the default given your :otp_app config
    # data_dir: "./priv/cldr",
    # Not required, this is the default
    # generate_docs: true,
    precompile_transliterations: [{:latn, :arab}, {:arab, :latn}],
    precompile_number_formats: ["#,##0.00 ¤", "¤#,##0.00"],
    # Shouldn't be required, locales are downloaded at compile time
    # force_locale_download: Mix.env() == :prod 
end
alaadahmed commented 3 years ago

Thank you, I modified them now. This is the first time for me to use CLDR in any application, but will not be last.

alaadahmed commented 3 years ago

I thought also, if I removed add_fallback_locales or I meant set it to false, this will remove the "root" locale, but it doesn't!

kipcole9 commented 3 years ago

You are right, the "root" locale is included no matter what you configure. Certain operations require data that is only stored in the "root" locale.

It's possible that I can't actually fix this issue since there is a compile time circular dependency. ex_cldr needs the Gettext backend to be compiled before it so that it can find out what locales are configured on it.

The Gettext backend, when there is a custom :plural_forms module configured, needs that module compiled before it.

Since the :plural_forms module is part of ex_cldr we end up in a dependency loop that can't be resolved.

I have a couple more experiments to try and will see where that leads.

alaadahmed commented 3 years ago

Ah now I understand the reason behind that error. I hope you will reach to some solution for that.

alaadahmed commented 3 years ago

Is it possible to tell ex_cldr about what locales are configured without Gettext, I mean enter them manually so it will be compiled successfully, as a workaround, then Gettext will be also compiled because ex_cldr already compiled!

kipcole9 commented 3 years ago

I think you might be right. Here's what I'm thinking (not yet proven if it will work satisfactorily):

  1. If not using a custom pluralizer, then configuration stays the same - specify the module name.
  2. If using a custom pluralizer, then the configuration should be a list of valid Gettext locales. To make this easy I'll add a function Cldr.Config.gettext_locales_from_directory/1 or some such name.
  3. If using a custom pluralizer and the configuration is a Gettext module, then no Gettext locales will be configured (because of the circular dependency issues) - but I will emit a warning.

I was concerned that there are other requirements to have the Gettext module name be always known. But the only requirement is in Cldr.Plug.SetLocale and I can preserve the current semantics while making the change above.

I may not finish this change until the weekend. Until then, I wonder if you really do need a custom pluralizer in your Gettext backend? Gettext's default pluralizer does a reasonable job so perhaps thats enough and you only configured a custom pluralizer because you thought one was required?

alaadahmed commented 3 years ago

I am not sure really, but I wanted to use it because I have the default one with Gettext doesn't convert numbers to arabic format. For example: I have <%= ngettext("1 minute read", "%{count} minutes read", read_time(post.content)) %>. This should be translated to 2 other languages "Arabic" and "German". For German there is no problem, but for Arabic, numbers appear in English i.e: 1,2,3 but I thought if I used the Cldr.Plural it will be in Arabic format i.e: ١،٢،٣،٤،٥. So for the mentioned example above, in Arabic it should be: "٣ دقائق قراءة" but what I get is: "3 دقائق قراءة" So what do you think? Actually I am not in hurry and you can take your time, but it will be very convenient addition if you could manage to solve it, I am not sure!

msgid "1 minute read"
msgid_plural "%{count} minutes read"
msgstr[0] "ثواني قراءة"
msgstr[1] "دقيقة واحدة قراءة"
msgstr[2] "دقيقتين قراءة"
msgstr[3] "%{count} دقائق قراءة"
msgstr[4] "%{count} دقيقة قراءة"
msgstr[5] "%{count} دقيقة قراءة"

And if this is not related to Cldr.Gettext.Plural or fixing this not related to issue that I mentioned, then I think will fallback to the default Gettext because no other solution.

alaadahmed commented 3 years ago
Screen Shot 2021-06-07 at 23 43 33

As you see, I used Cldr.Date.to_string to show date in arabic format, and this is fantastic, but when I used ngettext to show the reading time, numbers still appear in English.

kipcole9 commented 3 years ago

Ah, yes, I see your point. The thing is that Gettext doesn't know about scripts. So even a custom pluralizer won't help there. I see two approaches you might consider:

  1. Pre-format the number before you do message formatting. For example, instead of <%= ngettext("1 minute read", "%{count} minutes read", read_time(post.content)) %>, do (not tested!):

    <%= ngettext("1 minute read", "%{minutes} minutes read", read_time(post.content), minutes:  Cldr.Number.to_string(read_time(post.content), locale: "ar") %>
  2. It may be that ex_cldr_messages fits your needs better because it is designed for I18n and formats "everything" in a localised way. Its not a full translation library but @tmbb has been working on a very very cool library called ex_i18n that is an alternative to Gettext (and much better in my opinion).

alaadahmed commented 3 years ago

Thanks a lot, I will check that out. I think then you can close this issue if anyway Cldr.Gettext.Plural is experimental and not related to the issue.

kipcole9 commented 3 years ago

I really appreciate you opening the issue. It identified a bug (and a lack of tests) that I need to fix and thats very helpful.

True that its unrelated to the original challenge you had but I hope the options above will help get you there. I will close this now, and respond to the issue you raised on ex_cldr_numbers :-)