elixir-gettext / gettext Goto Github PK
View Code? Open in Web Editor NEWInternationalization and localization support for Elixir.
Home Page: https://hexdocs.pm/gettext
Internationalization and localization support for Elixir.
Home Page: https://hexdocs.pm/gettext
I have to investigate why. It basically just runs as mix test
without caching tests or even printing anything that mix test --stale
usually prints. @antipax if you have any quick idea on why this could be, feel free to chime in :)
The original gettext library has pgettext
, npgettext
, dpgettext
and dpngettext
macros to provide different translations of the same phrases in different context. Those are very useful in case of there are short strings and their implementation is simple.
When a non binary value is passed to one of the Gettext.*gettext
functions, the error is absolutely terrible (it goes down to Regex.split/3
, making it super cryptic):
iex(3)> Gettext.dgettext MyApp.Gettext, "default", :foo
** (FunctionClauseError) no function clause matching in Regex.split/3
(elixir) lib/regex.ex:375: Regex.split(~r/\n (?<left>) # Start, available through :left\n %{ # Literal '%{'\n
[^}]+ # One or more non-} characters\n } # Literal '}'\n (?<right>) # End, available through :right\n /x, :
foo, [on: [:left, :right], trim: true])
lib/gettext/interpolation.ex:27: Gettext.Interpolation.to_interpolatable/1
lib/gettext/interpolation.ex:99: Gettext.Interpolation.interpolate/2
(my_app) lib/my_app/gettext.ex:1: MyApp.Gettext.lgettext/4
lib/gettext.ex:480: Gettext.dgettext/4
How to use gettext dynamicly? I think code like:
string = "sample of string"
gettext string
have to work, because "string" variable return a string, it is logical, right?
Brand new phoenix app created with mix phoenix.new
on OTP 19 has these warnings.
Here's a repro app: https://github.com/aaronjensen/dialyzer_repro
Irrelevant warnings are omitted. Originally reported at phoenixframework/phoenix#1872
Starting Dialyzer
dialyzer --no_check_plt --plt /Users/aaronjensen/.dialyxir_core_19_1.3.2.plt -Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs /Users/aaronjensen/Source/dialyzer_repro/_build/dev/lib/dialyzer_repro/ebin
Proceeding with analysis...
gettext.ex:1: Function lngettext/6 has no local return
gettext.ex:1: The call 'Elixir.Gettext.Interpolation':interpolate(_@7::any(),_@8::#{'count':=_, _=>_}) breaks the contract (binary(),#{}) -> {'ok',binary()} | {'error',binary()}
gettext.ex:23: Function lngettext/5 has no local return
done in 0m3.37s
done (warnings were emitted)
Issue:
Updated comments in the .pot
file don't seem to have the change propogated to the .po
files on running mix gettext.merge priv/gettext
In .pot
files:
# This is a pot file and this is a comment
msgid "foo"
msgstr ""
changed to
# Sorry wrong comment first time
msgid "foo"
msgstr ""
doesn't seem to update in the .po
files
As per https://groups.google.com/forum/#!topic/elixir-lang-talk/GyYMj_4oc4o, mix gettext.extract
crashes when used with a variable domain name, such as dgettext(domain, "Say something")
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:domain, [line: 7], nil}
(elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir) lib/string/chars.ex:17: String.Chars.to_string/1
lib/gettext/extractor.ex:93: Gettext.Extractor.pot_path/2
lib/gettext/extractor.ex:89: Gettext.Extractor.create_po_struct/3
lib/gettext/extractor.ex:83: anonymous fn/3 in Gettext.Extractor.create_po_structs_from_extracted_translations/1
(stdlib) lists.erl:1262: :lists.foldl/3
lib/gettext/extractor.ex:81: Gettext.Extractor.create_po_structs_from_extracted_translations/1
lib/gettext/extractor.ex:62: Gettext.Extractor.pot_files/0
lib/mix/tasks/gettext.extract.ex:59: Mix.Tasks.Gettext.Extract.extract/0
lib/mix/tasks/gettext.extract.ex:30: Mix.Tasks.Gettext.Extract.run/1
(mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
Preferably I would not have it crash but rather just skip over the calls it cannot extract so that I can create those myself, and fill in the calls that it did extract.
When .pot
files are inside subdirectories (e.g., sub/foo.pot
), there's a match error which is the worst kind of error for users :).
We can do this after 0.1.0 is out and use the new jaro_distance from Elixir.
A PO file like this beauty (default.po
):
#: lib/foo.ex:1
msgid "hello"
#: lib/foo.ex:2
msgid "world"
msgstr ""
Spits out a basically impossible-to-decipher error:
** (Gettext.PO.SyntaxError) 4: syntax error before: <<"#: lib/foo.ex:2">>
Why this sucks (as if it was need to show explicitly 😛):
msgstr
for msgid "hello"
"@josevalim I'm afraid the only way to do this is to move the logic that builds translations from msgids and msgstrs outside of the .yrl
parser, in Elixir-land? Wdyt?
I may be missing something, but when running mix gettext.extract --merge
for the first time, I find all the message strings are blank. This results in all templates returning blank strings until I go fill in the translations. It seems like a better approach would be to have the msgid copied over to the msgstr. Thoughts?
Example of generated .po
#: web/views/page_view.ex:3
msgid "A productive web framework that does not compromise speed and maintainability."
msgstr ""
#: web/views/layout_view.ex:19
msgid "Get Started"
msgstr ""
Example of what I'd like to be generated:
#: web/views/page_view.ex:3
msgid "A productive web framework that does not compromise speed and maintainability."
msgstr "A productive web framework that does not compromise speed and maintainability."
#: web/views/layout_view.ex:19
msgid "Get Started"
msgstr "Get Started"
Brought up by phoenixframework/phoenix#1671.
Got:
** (Gettext.Error) missing interpolation keys: a
(gettext) lib/gettext.ex:653: Gettext.handle_backend_result/1
when call:
gettext("test %{a} %{a}", a: 1)
For
gettext("test %{a}", a: 1)
everything is fine
There is a difference between Simplified Chinese and Traditional Chinese, zh can't distinguish
the difference, usually we use zh_CN
for Simplified Chinese, zh_TW
for Traditional Chinese, like en_US
and en_GB
.
also see https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale
If you use interpolation to construct a string, and you provide it with missing keys, then it returns an error message telling you which keys are missing.
However, I need to use Gettext dynamically, and I would like it to do something a little different: If the key "key" is not present, then I would like it to return the string with place holder text "{key}".
For example, instead of this:
Localisation.Gettext.lgettext("en", "hello_world", "score-message", %{})
{:error, "missing interpolation keys: score"}
I would like it to return something like this:
Localisation.Gettext.lgettext("en", "hello_world", "score-message", %{})
{:ok, "You have scored {score} points."}
It would be great if the library could support this.
Unfortunately, it looks like this would be quite a big change, especially since the Interpolation module runs quite deeply in the code.
Is there any worth in creating a branch to implement this?
import MyApp.Gettext
gettext ~s(foo)
fails with ** (ArgumentError) msgid must be a string literal
.
We wrote a hex package for phoenix that makes form handling easier.
Unfortunately, we didn't find any way to use the mix gettext.extract
with it.
%User{first_name: "....", last_name: "....."}
(User model)
<%= input f, :first_name %>
(User form)
It calles:
Gettext.dgettext(MyApp.Gettext, "forms", "user.first_name.label")
Is there any way to automatically extract these translation-keys?
If a translation has translator comments, then merging it with itself will concat the comments. Minimal case:
t = %Gettext.PO.Translation{comments: ["# a comment"]}
Gettext.PO.Translations.merge(t, t)
#=> %Gettext.PO.Translation{comments: ["# a comment", "# a comment"], ...}
Gettext.PO.Translations.merge(t, Gettext.PO.Translations.merge(t, t))
#=> %Gettext.PO.Translation{comments: ["# a comment", "# a comment", "# a comment"], ...}
The ones in Phoenix are looking better, we should match them in Gettext: phoenixframework/phoenix@bd3abd8.
Opening this issue to lay down what @josevalim and I discussed yesterday. So, the next part in the development of Gettext for Elixir will be focused on compile-time niceties. These include automatic generation of POT and PO files as well as merging of such files.
First of all, just as a reference, POT files are just like PO files but with empty translations. Example of snippet of POT file:
msgid "Hello there!"
msgstr ""
msgid "One error"
msgid_plural "%{count} errors"
msgstr[0]
msgstr[1]
The first step for implementing all of this will be to create a Gettext.PO
struct that will represent a PO file. We need this since PO files contain translations as well as metadata (headers at the top of the file).
After we have a Gettext.PO
struct in place, we'll create the code that dumps an instance of this struct as a PO(T) file. The goal (and the ultimate test) will be to make this true:
dump(parse(file)) == file
Once this is done, the next step will be to write code that merges two PO(T) files. This is probably the trickiest thing to do. PO(T) files will be merged every time the user runs whatever task we'll provide and some PO(T) files already exist. During this step, we'll do this:
#: reference
comment (see #16 for some info on comments) assuming that we automatically generated themThis will be pretty easy at this point. We just need to tweak the *gettext
macros we have now so that we know all the translations at compile time.
We can probably refine the process of merging PO(T) files, for example by not removing translations based on string similarities.
This is everything I remember from our conversation, @josevalim if I missed something add it here so we have everything in one place :).
This is similar to, but slightly different than:
#67
phoenixframework/phoenix#1411
mix phoenix.server
== Compilation error on file lib/gettext/backend.ex ==
** (CompileError) lib/gettext/backend.ex:16: undefined function ::/2
(elixir) expanding macro: Kernel.@/1
lib/gettext/backend.ex:16: Gettext.Backend (module)
(elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8
==> gettext
could not compile dependency gettext, mix compile failed. You can recompile this dependency with `mix deps.compile gettext` or update it with `mix deps.update gettext`
I got erlang-parsetools and erlang-dev installed, and the error is same on ubuntu 15.10 and 16.04. Help please?
I think the documentation for the plural forms might contain a mistake.
It says here that you can configure the pluralizer in config/config.exs
but that did not work for me. Configuring it in the custom Gettext module when using use Gettext
does work though.
https://hex.pm/packages/gettext
Currently it does not show any license. You have to dig through the repo to figure out it's Apache 2.0
it would be helpful if one could learn this by looking at hex.pm directly
I read through the issues and found that it's a feature, but unfortunately not for me.
When running mix gettext.extract
all msgids from POT files that have a reference are deleted if they are not present in the extracted msgids.
I'm trying to extract gettext msgids from React components that we use in our Phoenix project.
Phoenix uses Webpack to bundle frontend assets and I found this handy loader that will extract msgids from Js(x) files when they are loaded via webpack.
All the msgids are dumped to a new POT file in priv/gettext (because I configured it so) and they all have references.
Now when I extract msgid using mix, the POT file is emptied :(
I can't change the behaviour of the loader without patching jsxgettext and jsxgettext-loader.
I could put the POT file in another directory, but right now the workflow for Phoenix and Reactis perfectly in sync and I would like to keep it that way.
Is there a possibility to force (Elixir) gettext to not delete msgids from a POT file?
Right now it requires extra work and affects performance (e.g. the usage of Gettext.with_locale/3
).
An use case for it is the construction of push notification messages.
mix.lock
"gettext": {:hex, :gettext, "0.11.0"},
Relevant mix.exs dialyzer configuration in project function
dialyzer: [
plt_add_deps: true,
plt_file: ".local.plt"
]
On a brand new elixir 1.2.5 phoenix project, I get the following output from the dialyzer.
mix dialyzer
gettext.ex:1: The inferred type for the 1st argument of 'MACRO-dgettext'/3 ({_,_}) is not a supertype of #{}, which is expected type for this argument in the callback of the 'Elixir.Gettext.Backend' behaviour
gettext.ex:1: The inferred type for the 1st argument of 'MACRO-dgettext'/4 ({_,_}) is not a supertype of #{}, which is expected type for this argument in the callback of the 'Elixir.Gettext.Backend' behaviour
gettext.ex:1: The inferred type for the 1st argument of 'MACRO-dngettext'/5 ({_,_}) is not a supertype of #{}, which is expected type for this argument in the callback of the 'Elixir.Gettext.Backend' behaviour
gettext.ex:1: The inferred type for the 1st argument of 'MACRO-dngettext'/6 ({_,_}) is not a supertype of #{}, which is expected type for this argument in the callback of the 'Elixir.Gettext.Backend' behaviour
otherwise we need a more informative error message for a following case:
iex(1)> Gettext.locale
nil
iex(2)> Gettext.with_locale("it", fn() -> end)
** (ArgumentError) locale/1 only accepts binary locales
lib/gettext.ex:432: Gettext.locale/1
lib/gettext.ex:568: Gettext.with_locale/2
iex(3)> Gettext.locale
"it"
As you see without :default_locale
env var it fails to reset locale back.
PO files provide very helpful feature called developer comments. It will be good if gettext.extract with support it.
gettext("Navigation") # Will be on top of page.
#: web/views/pages/sidebar_view.ex:100
#. "Will be on top of page."
msgid "Navigation"
msgstr ""
For now all the work has been done on Gettext internals and it is time to discuss the public API. What I would like to call is this:
MyApp.Gettext.gettext "hello world"
And this does a couple things:
.po
files. In order to be collected, the first argument needs to be a string at compilation time.:en
but the default can be customized with the :default_locale
option on use
. What is the official gettext API for setting locale?However, this discussion brings one main question: what should be the API for doing dynamic lookups?
One option is to do the dynamic lookup still using the gettext macro:
string = "this still works"
MyApp.Gettext.gettext string
And then, when generating the default .po
file, we would warn on such usages so users remember of checking those strings into their .po
files.
Another option though is to ask the users to go through the currently "private" lgettext
function for doing the dynamic lookup. In doing such, they would need to explicitly pass the locale, domain and what not.
The third and final option is to introduce another set of functions that would be used for the dynamic lookup. Or even provide an API in the main Gettext module itself:
string = "this still works"
Gettext.gettext(MyApp.Gettext, string)
The rationale behind this last one is that for dynamic lookups the gettext module will likely be dynamic too, so provide an API that receives both is ok.
Thoughts?
When I use Poedit it saves the .po file like this:
#: web/views/dashboard_view.ex:5 web/views/dashboard_view.ex:10
#: web/views/dashboard_view.ex:20
msgid "Memory"
msgstr "Minne"
with multiple source code references on same line (there is a wrap-at preference setting in poedit).
This makes gettext choke:
== Compilation error on file lib/brando/gettext.ex ==
** (MatchError) no match of right hand side value: ["web/views/dashboard_view.ex", "5 web/views/dashboard_view.ex", "10"]
lib/gettext/po/parser.ex:53: Gettext.PO.Parser.parse_reference/1
lib/gettext/po/parser.ex:47: anonymous fn/2 in Gettext.PO.Parser.extract_references/1
(elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
lib/gettext/po/parser.ex:45: Gettext.PO.Parser.extract_references/1
lib/gettext/po/parser.ex:34: Gettext.PO.Parser.to_struct/1
(elixir) lib/enum.ex:1043: anonymous fn/3 in Enum.map/2
(elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
(elixir) lib/enum.ex:1043: Enum.map/2
This looks to be a valid way to print references, at least according to the GNU gettext manual:
#: src/msgcmp.c:338 src/po-lex.c:699
#, c-format
msgid "found %d fatal error"
msgid_plural "found %d fatal errors"
msgstr[0] "s'ha trobat %d error fatal"
msgstr[1] "s'han trobat %d errors fatals"
This function would return all the locales in a given backend for which there are PO files. This is useful to know in advance if the locale you want to translate to has translations file or if Gettext would fallback to english.
The idea is that:
.pot
file changed for a given directory, for example "priv/gettext", we will call Mix.Tasks.Gettext.Merge.run ["priv/gettext"]
once all .pot files for that directory are writtenThis way we can perform merge efficiently too.
@josevalim and I have been discussing whether to use a handwritten parser or a yecc-generated parser for .po
files. Initially, I was in favour of using yecc in order to have a very declarative and easy-to-understand grammar. After implementing a first version of the parser using yecc, I gave implementing a handwritten parser a try and it was practically just as easy.
Since I couldn't decide which way was the best one, I pushed both implementation to my fork so that we can decide together.
Please note that both parsers require (a lot) of polishing, in particular the yecc-based one since I'm not sure that stuffing the .yrl
file in a src
directory is the right way to go.
I'm looking forward to your opinions!
I think this is almost the last step before a first usable version of Gettext, so I'm excited to discuss this topic :D
So, we need to generate POT files from calls to the *gettext
family of macros. We already have these macros working, so the work we have to do in the macros themselves is probably just a matter of adding a couple lines to each macro:
defmacro gettext(...) do
# ...
if we_want_to_collect_translations do
Gettext.Collector.collect(translation)
end
# ...
end
@josevalim, @ericmj, and I discussed possible strategies for "triggering" this POT file generation at ElixirConfEU 2015. These are the alternatives I remember from the conversation:
mix gettext.pot
and mix gettext.merge_po
or something on these lines.I can't remember anything else from the conversation :( What are your ideas on this?
Comments in Gettext are not like comments everywhere else, since you usually don't just ignore them.
Comments start with an hash (#
) character and go on until EOL. There are two main kinds of comments:
#
followed by some whitespace, most commonly #[space]
)#
followed by some specifier character)We will need to parse, tokenize and keep all those comments associated with each translation since they are necessary to rebuild PO files exactly like we found them. Also, we can obviously make use of the autogenerated comments.
# This is a translator comment.
msgid "foo"
msgstr "bar"
We'll keep them as is, nice and easy.
#. extracted-comments
#: reference
#, flag
#| msgid previous-untranslated-string
Extracted comments: they're written by the programmer, in the source code, for the translator. I think we should avoid implementing this for now as we would need some kind of macro to do this as well.
#. Hey translator, thanks!
...
Reference: file or list of files the next translation comes from. This is the easiest kind of comment to implement. If two translations with the same msgid are found, they're merged into one translation and both source files are listed here.
#: my_app/lib/foo.ex:84 my_app/lib/bar.ex:40
...
Flag: a list of flags/tags/format directives. Mostly used to denote the source language. We'll most likely automatically add elixir-format
here.
#, elixir-format
...
Fuzzy: related to the fuzzy
flag. Let's just leave this alone for now :|
We're going to add a :comments
field to the PluralTranslation
and Translation
structs, I'll think about the data structure for this. I'd go for a simple map or a Comments
struct, but we have to decide if the original order of comments found in PO files matters to us (when re-creating the PO files back). If it does, we can go with a list of tagged tuples like [{:reference, ...}, {:flags, ...}]
. We'll see :)
This is just a reminder for us to eventually do it. When Phoenix 1.2 is released, it will be Elixir 1.2 only and Elixir 1.2 already includes its own warning, so it should be safe to remove the task from gettext. :)
As of today, the output we get from parsing a .po
file is a list of translations (both plural and singular, [Gettext.PO.Translation | Gettext.PO.PluralTranslation]
. This is fine since we only need to support a handful of features in .po
files. More importantly, those features draw a clear line between singular and plural translations, since the only thing they share is a msgid
(and even that has a slightly different meaning because it needs a msgid_plural
counterpart in a plural translation).
Eventually, the number of features we need to support will grow since gettext supports other stuff (like contexts) and we also have to understand comments in .po
files and so on.
Since the differences between singular and plural translations are pretty much only the msgid(_plural)?
and msgstr
parts, we would have to duplicate all the other data about them (comments, context) in both structs. I think we want to avoid that, so I'm proposing to introduce a Gettext.PO.Entry
struct, the @type
of which would look something like:
@type %Gettext.PO.Entry{
translation: Gettext.PO.Translation.t | Gettext.PO.PluralTranslation.t,
context: binary,
comments: %{
translator: [binary],
ref: ...,
...
}
}
The name is inspired by the ruby-gettext project, which has a POEntry
class.
The format I suggested is just a proof of concept, I'm sure we could improve it. What do you think?
For .pot files we must say:
# msgids are often extracted from source code.
# Run "mix gettext.extract" to make them up to date.
For .po files we must say:
# msgids come from .pot files. Do not add, change or remove msgids manually, only msgstr.
# Use "mix gettext.extract --merge" or "mix gettext.merge" to merge .pot into .po.
We also need a mechanism to have those comments in pot files not leak into po.
$ mix gettext.merge priv/gettext --locale foo
** (Mix) No such directory: priv/gettext/foo/LC_MESSAGES
We should create those directories ourselves in Gettext and maybe log we created them.
That's because there is something in the tests themselves erasing beam files.
As long as they are writing to different files, we are good to go.
Opening this issue so that we don't forget this problem and we can discuss it here. Quoting what @josevalim wrote in #21:
we need to discuss what we are going to do with duplicated entries in the same .po file. What if we have the same msgid twice? It will probably emit a warning when compiling but the warnings won't be clear. We can probably keep a
HashDict
when we load the translations storing all msgid => line, if there is a duplicate, we could store it in a list which we return as result of parse, something like:{:dup_msgid, [line1, lin2, msgid]}
. Then the entity calling parse can decide if it is going to raise, print errors, etc. This is the approach used by Erlang's compiler.
The proposed solution looks good to me, I'll give implementing it a try and we'll see how it works out :).
Hello, when trying to compile dependencies in a new Phoenix 1.1.0 application the libary gettext fails to compile. It seems to not recgonize a module called :yecc
. I have reproduced the error with some other info that may be useful. let me know if there anything else you need.
$ elixir --version
Elixir 1.1.0
$ erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell
"18"
$ uname -a
Linux worker 3.13.0-57-generic #95-Ubuntu SMP Fri Jun 19 09:28:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
$ mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v1.1.0/phoenix_new-1.1.0.ez
Found existing archive(s): phoenix_new-1.0.4.ez.
Are you sure you want to replace them? [Yn] Y
creating .mix/archives/phoenix_new-1.1.0.ez
$ mix phoenix.new example
...
$ cd example
$ cat mix.lock
%{"connection": {:hex, :connection, "1.0.2"},
"cowboy": {:hex, :cowboy, "1.0.4"},
"cowlib": {:hex, :cowlib, "1.0.2"},
"decimal": {:hex, :decimal, "1.1.0"},
"ecto": {:hex, :ecto, "1.1.0"},
"fs": {:hex, :fs, "0.9.2"},
"gettext": {:hex, :gettext, "0.9.0"},
"phoenix": {:hex, :phoenix, "1.1.0"},
"phoenix_ecto": {:hex, :phoenix_ecto, "2.0.0"},
"phoenix_html": {:hex, :phoenix_html, "2.3.0"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.1"},
"plug": {:hex, :plug, "1.0.3"},
"poison": {:hex, :poison, "1.5.0"},
"poolboy": {:hex, :poolboy, "1.5.1"},
"postgrex": {:hex, :postgrex, "0.10.0"},
"ranch": {:hex, :ranch, "1.2.0"}}
$ mix deps.compile
==> connection
Compiled lib/connection.ex
Generated connection app
==> poolboy (compile)
Compiled src/poolboy_worker.erl
Compiled src/poolboy_sup.erl
Compiled src/poolboy.erl
==> decimal
Compiled lib/decimal.ex
Generated decimal app
==> poison
Compiled lib/poison.ex
Compiled lib/poison/decoder.ex
Compiled lib/poison/parser.ex
Compiled lib/poison/encoder.ex
Generated poison app
==> gettext
could not compile dependency :gettext, "mix compile" failed. You can recompile this dependency with "mix deps.compile gettext", update it with "mix deps.update gettext" or clean it with "mix deps.clean gettext"
** (UndefinedFunctionError) undefined function: :yecc.file/2 (module :yecc is not available)
:yecc.file('src/gettext_po_parser.yrl', [parserfile: 'src/gettext_po_parser.erl', report: true])
(mix) lib/mix/compilers/erlang.ex:84: anonymous fn/3 in Mix.Compilers.Erlang.compile/3
(elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
(mix) lib/mix/compilers/erlang.ex:83: Mix.Compilers.Erlang.compile/3
(elixir) lib/enum.ex:1043: anonymous fn/3 in Enum.map/2
(elixir) lib/enum.ex:1385: Enum."-reduce/3-lists^foldl/2-0-"/3
(elixir) lib/enum.ex:1043: Enum.map/2
(mix) lib/mix/tasks/compile.all.ex:19: anonymous fn/1 in Mix.Tasks.Compile.All.run/1
Although gettext does not support interpolation, we could support it out of the box. The reasoning is that interpolation is common and supporting it out of the box is going to allow us to do some optimizations similar to the ones Chris did in linguist.
The idea is that lgettext
and lngettext
will receive an extra argument with interpolation: a map or a list (where a list is internally converted to a map). A naïve way of supporting interpolation is this:
def lgettext("pt", "default", "hello world %{something}", interpolation) do
Gettext.Interpolation.interpolate("olá mundo %{something}", interpolation)
end
However, it would be better if we extracted all occurrence of "%{count}" or "%{whatever}" from strings during compilation time. In other words, we could compile to something like:
def lgettext("pt", "default", "hello world %{something}", interpolation) do
case interpolation do
# Check all keys are in interpolation
%{something: something} -> {:ok, "olá mundo " <> something}
# Otherwise detect which keys are missing and return an error
_ -> {:error, Gettext.Interpolation.missing_interpolation_keys(interpolation, [:something])
end
end
Where missing_interpolation_key
will look at the interpolation map, the required keys, and return a string saying: "missing interpolation keys: foo, bar, something".
Note though we still need to have the runtime behaviour, because the default messages need to be translated at runtime, as we never compile them. So the fallback clause still is:
def lgettext(_locale, _context, default, interpolation) do
case Gettext.Interpolation.interpolate(default, interpolation) do
{:ok, interpolated} -> {:default, interpolated}
{:error, _} = error -> error
end
end
Thoughts?
I just stumbled upon a problem with the mix gettext.extract
task. In our mix.exs
file we define an alias like this:
"gettext.extract": ["custom.gettext.extract", "gettext.extract"]
This does not work :( The code gets compiled but the POT file is never written.
However:
"gettext.extract": ["gettext.extract", "custom.gettext.extract"]
does work.
Our custom extract task looks like this.
def run(_) do
node_env = case Application.get_env(:papyrus_psp, :environment) do
:dev -> "development"
:prod -> "production"
:test -> "test"
_ -> "unknown"
end
case Mix.Shell.IO.cmd(command(node_env)) do
0 -> :ok
_ -> :error
end
end
defp command(node_env) do
"NODE_ENV=#{node_env} $(npm bin)/react-gettext-parser --output priv/gettext/react.pot 'web/static/js/**/*.{js,jsx}' '!web/static/js/i18n.js'"
end
It's not really a problem for us, but maybe someone else will run into the same problem, so I wanted to document it here.
Hi, it's me again ^^
The documentation says:
Locale
At runtime, all gettext-related functions and macros that do not explicitely take a locale as an argument read the locale from Gettext.get_locale/1. The locale can be set with Gettext.put_locale/2. Locales are expressed as strings (like "en" or "fr"); they can be arbitrary strings as long as they match a directory name.
However, I just found that this locale will be passed to Gettext.Plural.plural
if no other pluralizer is configured. I get a no function clause matching
for the locale nb-no
.
I think there should be a catch all clause to avoid problems when people use strange locales.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.