elixir-cldr / cldr

Elixir implementation of CLDR/ICU

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

alaadahmed opened this issue · comments

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

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!

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

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?

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

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.

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

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

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!

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.

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

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!

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?

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.

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.

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") %>
  1. 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).

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.

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 :-)