├── .dialyzer_ignore_warnings
├── .formatter.exs
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── bench
├── locale_and_options.exs
├── parse.exs
├── profile_number_parser.ex
├── profile_to_string.exs
└── to_string.exs
├── config
├── config.exs
├── dev.exs
├── prod.exs
├── release.exs
└── test.exs
├── lib
└── cldr
│ ├── exception.ex
│ ├── number.ex
│ ├── number
│ ├── backend
│ │ ├── compiler.ex
│ │ ├── decimal_formatter.ex
│ │ ├── format.ex
│ │ ├── number.ex
│ │ ├── rbnf.ex
│ │ ├── symbol.ex
│ │ ├── system.ex
│ │ └── transliterate.ex
│ ├── format.ex
│ ├── format
│ │ ├── compiler.ex
│ │ ├── meta.ex
│ │ └── options.ex
│ ├── formatter
│ │ ├── currency_formatter.ex
│ │ ├── decimal_formatter.ex
│ │ └── short_formatter.ex
│ ├── parse.ex
│ ├── rbnf.ex
│ ├── rbnf
│ │ ├── processor.ex
│ │ └── rule.ex
│ ├── string.ex
│ ├── symbol.ex
│ ├── system.ex
│ └── transliterate.ex
│ └── protocol
│ └── cldr_chars.ex
├── logo.png
├── mix.exs
├── mix.lock
├── mix
├── clause_debugger.ex_
├── for_dialyzer.ex
├── rbnf_test_data_gen.ex
├── tasks
│ └── generate_rbnf_test_data.ex
└── test_backend.ex
├── src
├── decimal_formats_lexer.xrl
├── decimal_formats_parser.yrl
├── rbnf_lexer.xrl
└── rbnf_parser.yrl
└── test
├── cldr_numbers_test.exs
├── doc_test.exs
├── meta_test.exs
├── number
├── currency_test.exs
├── decimal_format_compiler_test.exs
├── exponent_test.exs
├── number_format_test.exs
├── number_parser_test.exs
├── number_string_test.exs
├── number_symbol_test.exs
├── number_system_test.exs
├── rbnf_test.exs
├── short_formatter_test.exs
├── significant_digits_test.exs
├── split_format_test.exs
├── transliteration_test.exs
└── wrapper_test.exs
├── rbnf
└── rbnf_rule_compiler_test.exs
├── support
├── .gitkeep
├── icu_test_cases.txt
├── number_format_test_data.exs
├── rbnf
│ ├── af
│ │ └── rbnf_test.json
│ ├── ar-EG
│ │ └── rbnf_test.json
│ ├── be
│ │ └── rbnf_test.json
│ ├── bg
│ │ └── rbnf_test.json
│ ├── bn
│ │ └── rbnf_test.json
│ ├── ca
│ │ └── rbnf_test.json
│ ├── cs
│ │ └── rbnf_test.json
│ ├── cy
│ │ └── rbnf_test.json
│ ├── da
│ │ └── rbnf_test.json
│ ├── de-CH
│ │ └── rbnf_test.json
│ ├── de
│ │ └── rbnf_test.json
│ ├── el
│ │ └── rbnf_test.json
│ ├── en-150
│ │ └── rbnf_test.json
│ ├── en-AU
│ │ └── rbnf_test.json
│ ├── en-CA
│ │ └── rbnf_test.json
│ ├── en-GB
│ │ └── rbnf_test.json
│ ├── en-IE
│ │ └── rbnf_test.json
│ ├── en-SG
│ │ └── rbnf_test.json
│ ├── en-ZA
│ │ └── rbnf_test.json
│ ├── en
│ │ └── rbnf_test.json
│ ├── es-419
│ │ └── rbnf_test.json
│ ├── es-CO
│ │ └── rbnf_test.json
│ ├── es-MX
│ │ └── rbnf_test.json
│ ├── es-US
│ │ └── rbnf_test.json
│ ├── es
│ │ └── rbnf_test.json
│ ├── eu
│ │ └── rbnf_test.json
│ ├── fa
│ │ └── rbnf_test.json
│ ├── fi
│ │ └── rbnf_test.json
│ ├── fil
│ │ └── rbnf_test.json
│ ├── fr-BE
│ │ └── rbnf_test.json
│ ├── fr-CA
│ │ └── rbnf_test.json
│ ├── fr-CH
│ │ └── rbnf_test.json
│ ├── fr
│ │ └── rbnf_test.json
│ ├── ga
│ │ └── rbnf_test.json
│ ├── gl
│ │ └── rbnf_test.json
│ ├── gu
│ │ └── rbnf_test.json
│ ├── he
│ │ └── rbnf_test.json
│ ├── hi
│ │ └── rbnf_test.json
│ ├── hr
│ │ └── rbnf_test.json
│ ├── hu
│ │ └── rbnf_test.json
│ ├── id
│ │ └── rbnf_test.json
│ ├── is
│ │ └── rbnf_test.json
│ ├── it-CH
│ │ └── rbnf_test.json
│ ├── it
│ │ └── rbnf_test.json
│ ├── ja
│ │ └── rbnf_test.json
│ ├── kn
│ │ └── rbnf_test.json
│ ├── ko
│ │ └── rbnf_test.json
│ ├── lv
│ │ └── rbnf_test.json
│ ├── mr
│ │ └── rbnf_test.json
│ ├── ms
│ │ └── rbnf_test.json
│ ├── nb
│ │ └── rbnf_test.json
│ ├── nl
│ │ └── rbnf_test.json
│ ├── pl
│ │ └── rbnf_test.json
│ ├── pt
│ │ └── rbnf_test.json
│ ├── ro
│ │ └── rbnf_test.json
│ ├── ru
│ │ └── rbnf_test.json
│ ├── sk
│ │ └── rbnf_test.json
│ ├── sq
│ │ └── rbnf_test.json
│ ├── sr
│ │ └── rbnf_test.json
│ ├── sv
│ │ └── rbnf_test.json
│ ├── ta
│ │ └── rbnf_test.json
│ ├── th
│ │ └── rbnf_test.json
│ ├── tr
│ │ └── rbnf_test.json
│ ├── uk
│ │ └── rbnf_test.json
│ ├── ur
│ │ └── rbnf_test.json
│ ├── vi
│ │ └── rbnf_test.json
│ ├── zh-Hant
│ │ └── rbnf_test.json
│ └── zh
│ │ └── rbnf_test.json
├── rbnf_test_support.exs
├── split_format_test_data.exs
├── test_backend.ex
└── wrapper.ex
├── sync_test.exs
└── test_helper.exs
/.dialyzer_ignore_warnings:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | inputs: ["mix.exs", "{config,lib,test,mix}/**/*.{ex,exs}"],
3 | locals_without_parens: [docp: 1]
4 | ]
5 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Elixir CI
2 |
3 | # Define workflow that runs when changes are pushed to the
4 | # `main` branch or pushed to a PR branch that targets the `main`
5 | # branch. Change the branch name if your project uses a
6 | # different name for the main branch like "master" or "production".
7 | on:
8 | push:
9 | branches: [ "main" ] # adapt branch for project
10 | pull_request:
11 | branches: [ "main" ] # adapt branch for project
12 |
13 | # Sets the ENV `MIX_ENV` to `test` for running tests
14 | env:
15 | MIX_ENV: test
16 |
17 | permissions:
18 | contents: read
19 |
20 | jobs:
21 | test:
22 | # Set up a Postgres DB service. By default, Phoenix applications
23 | # use Postgres. This creates a database for running tests.
24 | # Additional services can be defined here if required.
25 | # services:
26 | # db:
27 | # image: postgres:12
28 | # ports: ['5432:5432']
29 | # env:
30 | # POSTGRES_PASSWORD: postgres
31 | # options: >-
32 | # --health-cmd pg_isready
33 | # --health-interval 10s
34 | # --health-timeout 5s
35 | # --health-retries 5
36 |
37 | runs-on: ubuntu-latest
38 | name: Test on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
39 | strategy:
40 | # Specify the OTP and Elixir versions to use when building
41 | # and running the workflow steps.
42 | matrix:
43 | otp: ['26.2.4'] # Define the OTP version [required]
44 | elixir: ['1.16.2-otp-26'] # Define the elixi
45 | steps:
46 | # Step: Setup Elixir + Erlang image as the base.
47 | - name: Set up Elixir
48 | uses: erlef/setup-beam@v1
49 | with:
50 | otp-version: ${{matrix.otp}}
51 | elixir-version: ${{matrix.elixir}}
52 |
53 | # Step: Check out the code.
54 | - name: Checkout code
55 | uses: actions/checkout@v3
56 |
57 | # Step: Define how to cache deps. Restores existing cache if present.
58 | - name: Cache deps
59 | id: cache-deps
60 | uses: actions/cache@v3
61 | env:
62 | cache-name: cache-elixir-deps
63 | with:
64 | path: deps
65 | key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
66 | restore-keys: |
67 | ${{ runner.os }}-mix-${{ env.cache-name }}-
68 |
69 | # Step: Define how to cache the `_build` directory. After the first run,
70 | # this speeds up tests runs a lot. This includes not re-compiling our
71 | # project's downloaded deps every run.
72 | - name: Cache compiled build
73 | id: cache-build
74 | uses: actions/cache@v3
75 | env:
76 | cache-name: cache-compiled-build
77 | with:
78 | path: _build
79 | key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }}
80 | restore-keys: |
81 | ${{ runner.os }}-mix-${{ env.cache-name }}-
82 | ${{ runner.os }}-mix-
83 |
84 | # Step: Download project dependencies. If unchanged, uses
85 | # the cached version.
86 | - name: Install dependencies
87 | run: mix deps.get
88 |
89 | # Step: Compile the project treating any warnings as errors.
90 | # Customize this step if a different behavior is desired.
91 | - name: Compiles without warnings
92 | run: mix compile --warnings-as-errors
93 |
94 | # Step: Check that the checked in code has already been formatted.
95 | # This step fails if something was found unformatted.
96 | # Customize this step as desired.
97 | # - name: Check Formatting
98 | # run: mix format --check-formatted
99 |
100 | # Step: Execute the tests.
101 | - name: Run tests
102 | run: mix test
103 |
104 | # Step: Execute dialyzer.
105 | - name: Run dialyzer
106 | run: mix dialyzer
107 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /cover
3 | /deps
4 | /doc
5 | /references
6 | *.snapshot
7 | erl_crash.dump
8 | *.ez
9 | *.tar
10 | .DS_Store
11 | /src/*.erl
12 |
13 | # The xml downloaded from Unicode
14 | /downloads
15 |
16 | # Ignore the generated json files
17 | /data
18 |
19 | # erl is always generated
20 | *.erl
21 |
22 | # asdf
23 | .tool-versions
24 |
25 | # mis
26 | mise.toml
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | Copyright 2017-2023 Kip Cole
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
6 | compliance with the License. You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software distributed under the License
11 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 | implied. See the License for the specific language governing permissions and limitations under the
13 | License.
14 |
--------------------------------------------------------------------------------
/bench/locale_and_options.exs:
--------------------------------------------------------------------------------
1 | Benchee.run(
2 | %{
3 | "Cldr.locale_and_backend_from" => fn -> Cldr.locale_and_backend_from([]) end
4 | },
5 | time: 10,
6 | memory_time: 2
7 | )
--------------------------------------------------------------------------------
/bench/parse.exs:
--------------------------------------------------------------------------------
1 | number = "1234567.00"
2 | locale = Cldr.Locale.new!("en", MyApp.Cldr)
3 |
4 | parse = fn x ->
5 | case Integer.parse(x) do
6 | {integer, ""} -> integer
7 | _other -> case Float.parse(x) do
8 | {float, ""} -> float
9 | _other -> x
10 | end
11 | end
12 | end
13 |
14 | Benchee.run(
15 | %{
16 | "Cldr.Number.Parser.parse" => fn -> Cldr.Number.Parser.parse(number) end,
17 | "Cldr.Number.Parser.parse(locale)" => fn -> Cldr.Number.Parser.parse(number, locale: locale) end,
18 | "Float.parse" => fn -> Float.parse(number) end,
19 | "Flexi parser" => fn -> parse.(number) end
20 | },
21 | time: 10,
22 | memory_time: 2
23 | )
--------------------------------------------------------------------------------
/bench/profile_number_parser.ex:
--------------------------------------------------------------------------------
1 | defmodule ProfileRunner do
2 | import ExProf.Macro
3 |
4 | @doc "analyze with profile macro"
5 | def do_analyze do
6 | profile do
7 | Cldr.Number.Parser.parse("1234.00")
8 | end
9 | end
10 |
11 | @doc "get analysis records and sum them up"
12 | def run do
13 | {records, _block_result} = do_analyze()
14 |
15 | records
16 | |> Enum.filter(&String.contains?(&1.function, "Cldr.Number.Parser"))
17 | |> ExProf.Analyzer.print
18 | end
19 |
20 | end
21 |
22 | ProfileRunner.run
23 |
--------------------------------------------------------------------------------
/bench/profile_to_string.exs:
--------------------------------------------------------------------------------
1 | defmodule ProfileRunner do
2 | import ExProf.Macro
3 |
4 | @doc "analyze with profile macro"
5 | def do_analyze do
6 | {:ok, options} = Cldr.Number.Format.Options.validate_options(0, MyApp.Cldr, [])
7 | profile do
8 | MyApp.Cldr.Number.to_string 1234, options
9 | end
10 | end
11 |
12 | @doc "get analysis records and sum them up"
13 | def run do
14 | {records, _block_result} = do_analyze()
15 |
16 | records
17 | |> Enum.filter(&String.contains?(&1.function, "Cldr.Number"))
18 | |> ExProf.Analyzer.print
19 | end
20 |
21 | end
22 |
23 | ProfileRunner.run
24 |
25 | #
26 | # Total: 215 100.00% 328 [ 1.53]
27 | # %Prof{
28 | # calls: 1,
29 | # function: "'Elixir.Cldr.Number.Formatter.Decimal':add_first_group/3",
30 | # percent: 0.0,
31 | # time: 0,
32 | # us_per_call: 0.0
33 | # }
--------------------------------------------------------------------------------
/bench/to_string.exs:
--------------------------------------------------------------------------------
1 | {:ok, options} = Cldr.Number.Format.Options.validate_options([], MyApp.Cldr, locale: Cldr.get_locale())
2 | number = 100000.55
3 |
4 | Benchee.run(
5 | %{
6 | "Number to_string" => fn -> MyApp.Cldr.Number.to_string number end,
7 | "Number to_string preformatted options" => fn -> MyApp.Cldr.Number.to_string number, options end,
8 | "Float to_string" => fn -> Float.to_string 10000.55 end,
9 | ":erlang.float_to_binary" => fn -> :erlang.float_to_binary(number, [:compact, decimals: 3]) end
10 | },
11 | time: 10,
12 | memory_time: 2
13 | )
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | import Config
4 |
5 | import_config "#{Mix.env()}.exs"
6 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :ex_cldr,
4 | default_backend: MyApp.Cldr,
5 | json_library: JSON
6 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elixir-cldr/cldr_numbers/1fb2f82370c262f83dc348a9781211926ee97fa9/config/prod.exs
--------------------------------------------------------------------------------
/config/release.exs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elixir-cldr/cldr_numbers/1fb2f82370c262f83dc348a9781211926ee97fa9/config/release.exs
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | # In test mode we compile and test all locales
2 | import Config
3 |
4 | config :ex_unit,
5 | case_load_timeout: 220_000,
6 | timeout: 120_000
7 |
8 | config :ex_cldr,
9 | default_backend: TestBackend.Cldr
10 |
--------------------------------------------------------------------------------
/lib/cldr/exception.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Rbnf.NoRuleForNumber do
2 | @moduledoc """
3 | Exception raised when an attempt is made to invoke an RBNF rule for a number
4 | that is not supported by that rule.
5 | """
6 | defexception [:message]
7 |
8 | def exception(message) do
9 | %__MODULE__{message: message}
10 | end
11 | end
12 |
13 | defmodule Cldr.Rbnf.NoRule do
14 | @moduledoc """
15 | Exception raised when an attempt is made to invoke an RBNF rule that
16 | is not supported for a given locale
17 | """
18 | defexception [:message]
19 |
20 | def exception(message) do
21 | %__MODULE__{message: message}
22 | end
23 | end
24 |
25 | defmodule Cldr.CurrencyAlreadyDefined do
26 | @moduledoc """
27 | Exception raised when an attempt is made to define a currency
28 | that already exists.
29 | """
30 | defexception [:message]
31 |
32 | def exception(message) do
33 | %__MODULE__{message: message}
34 | end
35 | end
36 |
37 | defmodule Cldr.CurrencyCodeInvalid do
38 | @moduledoc """
39 | Exception raised when an attempt is made to define a currency
40 | code that is invalid.
41 | """
42 | defexception [:message]
43 |
44 | def exception(message) do
45 | %__MODULE__{message: message}
46 | end
47 | end
48 |
49 | defmodule Cldr.NoNumberSymbols do
50 | @moduledoc """
51 | Exception raised when when there are no number
52 | symbols for a locale and number system.
53 | """
54 | defexception [:message]
55 |
56 | def exception(message) do
57 | %__MODULE__{message: message}
58 | end
59 | end
60 |
61 | defmodule Cldr.Number.ParseError do
62 | @moduledoc """
63 | Exception raised when when trying to parse
64 | a string into a number and the string is
65 | not parseable.
66 | """
67 | defexception [:message]
68 |
69 | def exception(message) do
70 | %__MODULE__{message: message}
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/lib/cldr/number/backend/compiler.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Backend do
2 | @moduledoc false
3 |
4 | def define_number_modules(config) do
5 | quote location: :keep do
6 | unquote(Cldr.Number.Backend.Number.define_number_module(config))
7 | unquote(Cldr.Number.Backend.Format.define_number_module(config))
8 | unquote(Cldr.Number.Backend.Transliterate.define_number_module(config))
9 | unquote(Cldr.Number.Backend.System.define_number_module(config))
10 | unquote(Cldr.Number.Backend.Symbol.define_number_module(config))
11 | unquote(Cldr.Number.Backend.Decimal.Formatter.define_number_module(config))
12 | unquote(Cldr.Number.Backend.Rbnf.define_number_modules(config))
13 |
14 | # Number requires Currency
15 | unquote(Cldr.Currency.Backend.define_currency_module(config))
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/cldr/number/backend/decimal_formatter.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Backend.Decimal.Formatter do
2 | @moduledoc false
3 |
4 | def define_number_module(config) do
5 | alias Cldr.Number.Formatter.Decimal
6 |
7 | backend = config.backend
8 |
9 | quote location: :keep do
10 | defmodule Number.Formatter.Decimal do
11 | unless Cldr.Config.include_module_docs?(unquote(config.generate_docs)) do
12 | @moduledoc false
13 | end
14 |
15 | alias Cldr.Number.Formatter.Decimal
16 | alias Cldr.Number.Format.Compiler
17 | alias Cldr.Number.Format.Meta
18 | alias Cldr.Number.Format.Options
19 | alias Cldr.Math
20 |
21 | @doc """
22 | Formats a number according to a decimal format string.
23 |
24 | ## Arguments
25 |
26 | * `number` is an integer, float or Decimal
27 |
28 | * `format` is a format string. See `#{inspect(unquote(backend))}.Number` for further information.
29 |
30 | * `options` is a map of options. See `#{inspect(unquote(backend))}.Number.to_string/2`
31 | for further information.
32 |
33 | """
34 | @dialyzer {:nowarn_function, to_string: 3}
35 |
36 | @spec to_string(
37 | Math.number_or_decimal(),
38 | String.t() | Meta.t(),
39 | Keyword.t() | Options.t()
40 | ) ::
41 | {:ok, String.t()} | {:error, {module(), String.t()}}
42 |
43 | def to_string(number, format, options \\ [])
44 |
45 | def to_string(number, format, options) when is_binary(format) and is_list(options) do
46 | with {:ok, options} <- Options.validate_options(number, unquote(backend), options) do
47 | to_string(number, format, options)
48 | end
49 | end
50 |
51 | # Precompile the known formats and build the formatting pipeline
52 | # specific to this format thereby optimizing the performance.
53 | unquote(Decimal.define_to_string(backend))
54 |
55 | # Other number formatting systems may create the formatting
56 | # metadata by other means (like a printf function) in which
57 | # case we don't do anything except format
58 | def to_string(number, %Meta{} = meta, %Options{} = options) do
59 | meta = Decimal.update_meta(meta, number, unquote(backend), options)
60 | Decimal.do_to_string(number, meta, unquote(backend), options)
61 | end
62 |
63 | def to_string(number, %Meta{} = meta, options) do
64 | with {:ok, options} <- Options.validate_options(number, unquote(backend), options) do
65 | to_string(number, meta, options)
66 | end
67 | end
68 |
69 | # For formats not precompiled we need to compile first
70 | # and then process. This will be slower than a compiled
71 | # format since we have to (a) compile the format and (b)
72 | # execute the full formatting pipeline.
73 | require Compiler
74 |
75 | def to_string(number, format, %Options{} = options) when is_binary(format) do
76 | Compiler.maybe_log_compile_warning(
77 | format,
78 | unquote(config.suppress_warnings),
79 | "ex_cldr_numbers: number format #{inspect(format)} is being compiled. " <>
80 | "For performance reasons please consider adding this format to the " <>
81 | "`precompile_number_formats` list in the backend configuration."
82 | )
83 |
84 | case Compiler.format_to_metadata(format) do
85 | {:ok, meta} ->
86 | meta = Decimal.update_meta(meta, number, unquote(backend), options)
87 | Decimal.do_to_string(number, meta, unquote(backend), options)
88 |
89 | {:error, message} ->
90 | {:error, {Cldr.FormatCompileError, message}}
91 | end
92 | end
93 | end
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/lib/cldr/number/backend/rbnf.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Backend.Rbnf do
2 | @moduledoc false
3 |
4 | def define_number_modules(config) do
5 | backend = config.backend
6 | root_locale = Cldr.Config.root_locale_name()
7 |
8 | quote location: :keep do
9 | defmodule Rbnf.NumberSystem do
10 | @moduledoc false
11 | if Cldr.Config.include_module_docs?(unquote(config.generate_docs)) do
12 | @moduledoc """
13 | Functions to implement the number system rule-based-number-format rules of CLDR.
14 |
15 | These rules are defined only on the "und" locale and represent specialised
16 | number formatting.
17 |
18 | The standard public API for RBNF is via the `Cldr.Number.to_string/2` function.
19 |
20 | The functions on this module are defined at compile time based upon the RBNF rules
21 | defined in the Unicode CLDR data repository. Available rules are identified by:
22 |
23 | iex> #{inspect(__MODULE__)}.rule_sets(#{inspect(unquote(root_locale))})
24 | ...> |> Enum.sort()
25 | [
26 | :armenian_lower,
27 | :armenian_upper,
28 | :cyrillic_lower,
29 | :ethiopic,
30 | :georgian,
31 | :greek_lower,
32 | :greek_upper,
33 | :hebrew,
34 | :hebrew_item,
35 | :roman_lower,
36 | :roman_upper,
37 | :tamil,
38 | :zz_default
39 | ]
40 |
41 | A rule can then be invoked on an available rule_set. For example
42 |
43 | iex> #{inspect(__MODULE__)}.roman_upper(123, #{inspect(unquote(root_locale))})
44 | "CXXIII"
45 |
46 | This particular call is equivalent to the call through the public API of:
47 |
48 | iex> #{inspect(unquote(backend))}.Number.to_string(123, format: :roman)
49 | {:ok, "CXXIII"}
50 |
51 | """
52 | end
53 |
54 | import Kernel, except: [and: 2]
55 | use Cldr.Rbnf.Processor, backend: unquote(backend)
56 |
57 | define_rules(:NumberingSystemRules, unquote(backend), __ENV__)
58 | end
59 |
60 | defmodule Rbnf.Spellout do
61 | @moduledoc false
62 | if Cldr.Config.include_module_docs?(unquote(config.generate_docs)) do
63 | @moduledoc """
64 | Functions to implement the spellout rule-based-number-format rules of CLDR.
65 |
66 | As CLDR notes, the data is incomplete or non-existent for many languages. It
67 | is considered complete for English however.
68 |
69 | The standard public API for RBNF is via the `Cldr.Number.to_string/2` function.
70 |
71 | The functions on this module are defined at compile time based upon the RBNF rules
72 | defined in the Unicode CLDR data repository. Available rules are identified by:
73 |
74 | iex> #{inspect(__MODULE__)}.rule_sets("en")
75 | ...> |> Enum.sort()
76 | [
77 | :spellout_cardinal,
78 | :spellout_cardinal_verbose,
79 | :spellout_numbering,
80 | :spellout_numbering_verbose,
81 | :spellout_numbering_year,
82 | :spellout_ordinal,
83 | :spellout_ordinal_verbose
84 | ]
85 |
86 | A rule can then be invoked on an available rule_set. For example:
87 |
88 | iex> #{inspect(__MODULE__)}.spellout_ordinal(123, "en")
89 | "one hundred twenty-third"
90 |
91 | This call is equivalent to the call through the public API of:
92 |
93 | iex> #{inspect(unquote(backend))}.Number.to_string(123, format: :spellout)
94 | {:ok, "one hundred twenty-three"}
95 |
96 | """
97 | end
98 |
99 | import Kernel, except: [and: 2]
100 | use Cldr.Rbnf.Processor, backend: unquote(backend)
101 |
102 | define_rules(:SpelloutRules, unquote(backend), __ENV__)
103 | end
104 |
105 | defmodule Rbnf.Ordinal do
106 | @moduledoc false
107 | if Cldr.Config.include_module_docs?(unquote(config.generate_docs)) do
108 | @moduledoc """
109 | Functions to implement the ordinal rule-based-number-format rules of CLDR.
110 |
111 | As CLDR notes, the data is incomplete or non-existent for many languages. It
112 | is considered complete for English however.
113 |
114 | The standard public API for RBNF is via the `Cldr.Number.to_string/2` function.
115 |
116 | The functions on this module are defined at compile time based upon the RBNF rules
117 | defined in the Unicode CLDR data repository. Available rules are identified by:
118 |
119 | iex> #{inspect(__MODULE__)}.rule_sets(:en)
120 | [:digits_ordinal]
121 |
122 | iex> #{inspect(__MODULE__)}.rule_sets("fr")
123 | ...> |> Enum.sort()
124 | [
125 | :digits_ordinal,
126 | :digits_ordinal_feminine,
127 | :digits_ordinal_feminine_plural,
128 | :digits_ordinal_masculine,
129 | :digits_ordinal_masculine_plural
130 | ]
131 |
132 | A rule can then be invoked on an available rule_set. For example
133 |
134 | iex> #{inspect(__MODULE__)}.digits_ordinal(123, :en)
135 | "123rd"
136 |
137 | This call is equivalent to the call through the public API of:
138 |
139 | iex> #{inspect(unquote(backend))}.Number.to_string(123, format: :ordinal)
140 | {:ok, "123rd"}
141 |
142 | """
143 | end
144 |
145 | import Kernel, except: [and: 2]
146 | use Cldr.Rbnf.Processor, backend: unquote(backend)
147 |
148 | define_rules(:OrdinalRules, unquote(backend), __ENV__)
149 | end
150 | end
151 | end
152 | end
153 |
--------------------------------------------------------------------------------
/lib/cldr/number/backend/symbol.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Backend.Symbol do
2 | @moduledoc false
3 |
4 | def define_number_module(config) do
5 | module = inspect(__MODULE__)
6 | backend = config.backend
7 | config = Macro.escape(config)
8 |
9 | quote location: :keep, bind_quoted: [module: module, backend: backend, config: config] do
10 | defmodule Number.Symbol do
11 | unless Cldr.Config.include_module_docs?(config.generate_docs) do
12 | @moduledoc false
13 | end
14 |
15 | all_symbols =
16 | for locale <- Cldr.Locale.Loader.known_locale_names(config) do
17 | symbols =
18 | locale
19 | |> Cldr.Locale.Loader.get_locale(config)
20 | |> Map.get(:number_symbols)
21 | |> Enum.map(fn
22 | {number_system, nil} -> {number_system, nil}
23 | {number_system, symbols} -> {number_system, struct(Cldr.Number.Symbol, symbols)}
24 | end)
25 | |> Enum.into(%{})
26 |
27 | {locale, symbols}
28 | end
29 | |> Map.new()
30 |
31 | @doc """
32 | Returns a map of `Cldr.Number.Symbol.t` structs of the number symbols for each
33 | of the number systems of a locale.
34 |
35 | ## Options
36 |
37 | * `locale` is any valid locale name returned by
38 | `#{inspect(backend)}.known_locale_names/0`
39 | or a `Cldr.LanguageTag` struct returned by
40 | `#{inspect(backend)}.Locale.new!/1`. The default
41 | is `#{inspect(backend)}.get_locale/0`.
42 |
43 | ## Example:
44 |
45 | iex> #{inspect(__MODULE__)}.number_symbols_for(:th)
46 | {
47 | :ok,
48 | %{
49 | latn: %Cldr.Number.Symbol{
50 | decimal: %{standard: "."},
51 | exponential: "E",
52 | group: %{standard: ","},
53 | infinity: "∞",
54 | list: ";",
55 | minus_sign: "-",
56 | nan: "NaN",
57 | per_mille: "‰",
58 | percent_sign: "%",
59 | plus_sign: "+",
60 | superscripting_exponent: "×",
61 | time_separator: ":"
62 | },
63 | thai: %Cldr.Number.Symbol{
64 | decimal: %{standard: "."},
65 | exponential: "E",
66 | group: %{standard: ","},
67 | infinity: "∞",
68 | list: ";",
69 | minus_sign: "-",
70 | nan: "NaN",
71 | per_mille: "‰",
72 | percent_sign: "%",
73 | plus_sign: "+",
74 | superscripting_exponent: "×",
75 | time_separator: ":"
76 | }
77 | }
78 | }
79 |
80 | """
81 | @spec number_symbols_for(LanguageTag.t() | Cldr.Locale.locale_name()) ::
82 | {:ok, map()} | {:error, {module(), String.t()}}
83 |
84 | def number_symbols_for(locale \\ unquote(backend).get_locale())
85 |
86 | for {locale, symbols} <- all_symbols do
87 | def number_symbols_for(%LanguageTag{cldr_locale_name: unquote(locale)}) do
88 | {:ok, unquote(Macro.escape(symbols))}
89 | end
90 | end
91 |
92 | def number_symbols_for(locale_name) do
93 | with {:ok, locale} <- unquote(backend).validate_locale(locale_name) do
94 | number_symbols_for(locale)
95 | end
96 | end
97 |
98 | def number_symbols_for(locale, number_system) do
99 | with {:ok, system_name} <-
100 | unquote(backend).Number.System.system_name_from(number_system, locale),
101 | {:ok, symbols} <- number_symbols_for(locale) do
102 | symbols
103 | |> Map.get(system_name)
104 | |> Cldr.Number.Symbol.symbols_return(locale, number_system)
105 | end
106 | end
107 |
108 | all_decimal_symbols =
109 | for {_locale, locale_symbols} <- all_symbols,
110 | {_number_system, symbols} <- locale_symbols,
111 | !is_nil(symbols) do
112 | Map.values(symbols.decimal)
113 | end
114 | |> Elixir.List.flatten()
115 | |> Enum.uniq()
116 |
117 | all_grouping_symbols =
118 | for {_locale, locale_symbols} <- all_symbols,
119 | {_number_system, symbols} <- locale_symbols,
120 | !is_nil(symbols) do
121 | Map.values(symbols.group)
122 | end
123 | |> Elixir.List.flatten()
124 | |> Enum.uniq()
125 |
126 | @doc """
127 | Returns a list of all decimal symbols defined
128 | by the locales configured in this backend as
129 | a list.
130 |
131 | """
132 | def all_decimal_symbols do
133 | unquote(Macro.escape(all_decimal_symbols))
134 | end
135 |
136 | @doc """
137 | Returns a list of all grouping symbols defined
138 | by the locales configured in this backend as
139 | a list.
140 |
141 | """
142 | def all_grouping_symbols do
143 | unquote(Macro.escape(all_grouping_symbols))
144 | end
145 |
146 | @doc """
147 | Returns a list of all decimal symbols defined
148 | by the locales configured in this backend as
149 | a string.
150 |
151 | This string can be used as a character class
152 | when builing a regular expression.
153 |
154 | """
155 | def all_decimal_symbols_class do
156 | unquote(Macro.escape(all_decimal_symbols))
157 | end
158 |
159 | @doc """
160 | Returns a list of all grouping symbols defined
161 | by the locales configured in this backend as
162 | a string.
163 |
164 | This string can be used as a character class
165 | when builing a regular expression.
166 |
167 | """
168 | def all_grouping_symbols_class do
169 | unquote(Enum.join(all_grouping_symbols))
170 | end
171 | end
172 | end
173 | end
174 | end
175 |
--------------------------------------------------------------------------------
/lib/cldr/number/backend/transliterate.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Backend.Transliterate do
2 | @moduledoc false
3 |
4 | def define_number_module(config) do
5 | module = inspect(__MODULE__)
6 | backend = config.backend
7 | config = Macro.escape(config)
8 |
9 | quote location: :keep, bind_quoted: [module: module, backend: backend, config: config] do
10 | defmodule Number.Transliterate do
11 | @moduledoc false
12 | if Cldr.Config.include_module_docs?(config.generate_docs) do
13 | @moduledoc """
14 | Transliteration for digits and separators.
15 |
16 | Transliterating a string is an expensive business. First the string has to
17 | be exploded into its component graphemes. Then for each grapheme we have
18 | to map to the equivalent in the other `{locale, number_system}`. Then we
19 | have to reassemble the string.
20 |
21 | Effort is made to short circuit where possible. Transliteration is not
22 | required for any `{locale, number_system}` that is the same as `{"en",
23 | "latn"}` since the implementation uses this combination for the placeholders during
24 | formatting already. When short circuiting is possible (typically the en-*
25 | locales with "latn" number_system - the total number of short circuited
26 | locales is 211 of the 537 in CLDR) the overall number formatting is twice as
27 | fast than when formal transliteration is required.
28 |
29 | ### Configuring precompilation of digit transliterations
30 |
31 | This module includes `Cldr.Number.Transliterate.transliterate_digits/3` which transliterates
32 | digits between number systems. For example from :arabic to :latn. Since generating a
33 | transliteration map is slow, pairs of transliterations can be configured so that the
34 | transliteration map is created at compile time and therefore speeding up transliteration at
35 | run time.
36 |
37 | To configure these transliteration pairs, add the to the `use Cldr` configuration
38 | in a backend module:
39 |
40 | defmodule MyApp.Cldr do
41 | use Cldr,
42 | locale: ["en", "fr", "th"],
43 | default_locale: "en",
44 | precompile_transliterations: [{:latn, :thai}, {:arab, :thai}]
45 | end
46 |
47 | Where each tuple in the list configures one transliteration map. In this example, two maps are
48 | configured: from `:latn` to `:thai` and from `:arab` to `:thai`.
49 |
50 | A list of configurable number systems is returned by `Cldr.Number.System.numeric_systems/0`.
51 |
52 | If a transliteration is requested between two number pairs that have not been configured for
53 | precompilation, a warning is logged.
54 |
55 | """
56 | end
57 |
58 | alias Cldr.Number.System
59 | alias Cldr.Number.Symbol
60 | alias Cldr.Number.Format.Compiler
61 | alias Cldr.LanguageTag
62 | alias Cldr.Config
63 |
64 | @doc """
65 | Transliterates from latin digits to another number system's digits.
66 |
67 | Transliterates the latin digits 0..9 to their equivalents in
68 | another number system. Also transliterates the decimal and grouping
69 | separators as well as the plus, minus and exponent symbols. Any other character
70 | in the string will be returned "as is".
71 |
72 | ## Arguments
73 |
74 | * `sequence` is the string to be transliterated.
75 |
76 | * `locale` is any known locale, defaulting to `#{inspect(backend)}.get_locale/0`.
77 |
78 | * `number_system` is any known number system. If expressed as a `string` it
79 | is the actual name of a known number system. If epressed as an `atom` it is
80 | used as a key to look up a number system for the locale (the usual keys are
81 | `:default` and `:native` but :traditional and :finance are also part of the
82 | standard). See `#{inspect(backend)}.Number.System.number_systems_for/1` for a locale to
83 | see what number system types are defined. The default is `:default`.
84 |
85 | For available number systems see `Cldr.Number.System.number_systems/0`
86 | and `#{inspect(backend)}.Number.System.number_systems_for/1`. Also see
87 | `#{inspect(backend)}.Number.Symbol.number_symbols_for/1`.
88 |
89 |
90 | ## Examples
91 |
92 | iex> #{inspect(__MODULE__)}.transliterate("123556")
93 | "123556"
94 |
95 | iex> #{inspect(__MODULE__)}.transliterate("123,556.000", "fr", :default)
96 | "123 556,000"
97 |
98 | iex> #{inspect(__MODULE__)}.transliterate("123556", "th", :default)
99 | "123556"
100 |
101 | iex> #{inspect(__MODULE__)}.transliterate("123556", "th", "thai")
102 | "๑๒๓๕๕๖"
103 |
104 | iex> #{inspect(__MODULE__)}.transliterate("123556", "th", :native)
105 | "๑๒๓๕๕๖"
106 |
107 | iex> #{inspect(__MODULE__)}.transliterate("Some number is: 123556", "th", "thai")
108 | "Some number is: ๑๒๓๕๕๖"
109 |
110 | """
111 |
112 | @spec transliterate(
113 | String.t(),
114 | LanguageTag.t() | Cldr.Locale.locale_name(),
115 | Cldr.Number.System.system_name() | Cldr.Number.System.types(),
116 | map() | Keyword.t()
117 | ) ::
118 | String.t() | {:error, {module(), String.t()}}
119 |
120 | def transliterate(
121 | sequence,
122 | locale \\ unquote(backend).get_locale(),
123 | number_system \\ System.default_number_system_type(),
124 | options \\ %{}
125 | )
126 |
127 | def transliterate(sequence, locale, number_system, options) when is_list(options) do
128 | transliterate(sequence, locale, number_system, Map.new(options))
129 | end
130 |
131 | # No transliteration required when the digits and separators as the same
132 | # as the ones we use in formatting.
133 | with {:ok, systems} <- Config.known_number_systems_like(:en, :latn, config) do
134 | for {locale, system} <- systems do
135 | def transliterate(
136 | sequence,
137 | %LanguageTag{cldr_locale_name: unquote(locale)},
138 | unquote(system),
139 | options
140 | ) do
141 | sequence
142 | end
143 | end
144 | end
145 |
146 | # We can only transliterate if the target {locale, number_system} has defined
147 | # digits. Some systems don't have digits, just rules.
148 | for {number_system, %{digits: _digits}} <- System.numeric_systems() do
149 | def transliterate(sequence, locale, unquote(number_system), options) do
150 | sequence
151 | |> String.graphemes()
152 | |> Enum.map(&transliterate_char(&1, locale, unquote(number_system), options))
153 | |> Elixir.List.to_string()
154 | end
155 | end
156 |
157 | # String locale name needs validation
158 | def transliterate(sequence, locale_name, number_system, options) when is_binary(locale_name) do
159 | with {:ok, locale} <- Module.concat(unquote(backend), :Locale).new(locale_name) do
160 | transliterate(sequence, locale, number_system, options)
161 | end
162 | end
163 |
164 | # For when the system name is not known (because its probably a system type
165 | # like :default, or :native)
166 | def transliterate(sequence, locale_name, number_system, options) do
167 | with {:ok, system_name} <-
168 | System.system_name_from(number_system, locale_name, unquote(backend)) do
169 | transliterate(sequence, locale_name, system_name, options)
170 | end
171 | end
172 |
173 | def transliterate!(sequence, locale, number_system, options) do
174 | case transliterate(sequence, locale, number_system, options) do
175 | {:error, {exception, reason}} -> raise exception, reason
176 | string -> string
177 | end
178 | end
179 |
180 | # Functions to transliterate the symbols
181 | for locale_name <- Cldr.Locale.Loader.known_locale_names(config),
182 | {name, symbols} <- Config.number_symbols_for!(locale_name, config),
183 | !is_nil(symbols) do
184 | # Mapping for the grouping separator
185 | defp transliterate_char(
186 | unquote(Compiler.placeholder(:group)),
187 | %LanguageTag{cldr_locale_name: unquote(locale_name)},
188 | unquote(name),
189 | options
190 | ) do
191 | group_symbols = unquote(Macro.escape(symbols.group))
192 | group_option = Map.get(options, :separators, :standard)
193 | Map.get(group_symbols, group_option)
194 | end
195 |
196 | # Mapping for the decimal separator
197 | defp transliterate_char(
198 | unquote(Compiler.placeholder(:decimal)),
199 | %LanguageTag{cldr_locale_name: unquote(locale_name)},
200 | unquote(name),
201 | options
202 | ) do
203 | decimal_symbols = unquote(Macro.escape(symbols.decimal))
204 | decimal_option = Map.get(options, :separators, :standard)
205 | Map.get(decimal_symbols, decimal_option)
206 | end
207 |
208 | # Mapping for the exponent
209 | defp transliterate_char(
210 | unquote(Compiler.placeholder(:exponent)),
211 | %LanguageTag{cldr_locale_name: unquote(locale_name)},
212 | unquote(name),
213 | _options
214 | ) do
215 | unquote(symbols.exponential)
216 | end
217 |
218 | # Mapping for the plus sign
219 | defp transliterate_char(
220 | unquote(Compiler.placeholder(:plus)),
221 | %LanguageTag{cldr_locale_name: unquote(locale_name)},
222 | unquote(name),
223 | _options
224 | ) do
225 | unquote(symbols.plus_sign)
226 | end
227 |
228 | # Mapping for the minus sign
229 | defp transliterate_char(
230 | unquote(Compiler.placeholder(:minus)),
231 | %LanguageTag{cldr_locale_name: unquote(locale_name)},
232 | unquote(name),
233 | _options
234 | ) do
235 | unquote(symbols.minus_sign)
236 | end
237 | end
238 |
239 | # Functions to transliterate the digits
240 | for {name, %{digits: digits}} <- System.numeric_systems() do
241 | graphemes = String.graphemes(digits)
242 |
243 | for latin_digit <- 0..9 do
244 | grapheme = :lists.nth(latin_digit + 1, graphemes)
245 | latin_char = Integer.to_string(latin_digit)
246 |
247 | defp transliterate_char(unquote(latin_char), _locale, unquote(name), _options) do
248 | unquote(grapheme)
249 | end
250 | end
251 | end
252 |
253 | # Any unknown mapping gets returned as is
254 | defp transliterate_char(digit, _locale, _name, _options) do
255 | digit
256 | end
257 |
258 | @doc """
259 | Transliterates digits from one number system to another number system
260 |
261 | * `digits` is binary representation of a number
262 |
263 | * `from_system` and `to_system` are number system names in atom form. See
264 | `Cldr.Number.System.numeric_systems/0` for available number systems.
265 |
266 | ## Example
267 |
268 | iex> #{inspect(__MODULE__)}.transliterate_digits "٠١٢٣٤٥٦٧٨٩", :arab, :latn
269 | "0123456789"
270 |
271 | """
272 | @spec transliterate_digits(String.t(), System.system_name(), System.system_name()) ::
273 | String.t()
274 |
275 | for {from_system, to_system} <- Map.get(config, :precompile_transliterations, []) do
276 | with {:ok, from} = System.number_system_digits(from_system),
277 | {:ok, to} = System.number_system_digits(to_system),
278 | map = System.generate_transliteration_map(from, to) do
279 | def transliterate_digits(digits, unquote(from_system), unquote(to_system)) do
280 | do_transliterate_digits(digits, unquote(Macro.escape(map)))
281 | end
282 | end
283 | end
284 |
285 | def transliterate_digits(digits, from_system, to_system) when is_binary(digits) do
286 | Cldr.Number.Transliterate.transliterate_digits(digits, from_system, to_system)
287 | end
288 |
289 | defp do_transliterate_digits(digits, map) do
290 | digits
291 | |> String.graphemes()
292 | |> Enum.map(&Map.get(map, &1, &1))
293 | |> Enum.join()
294 | end
295 | end
296 | end
297 | end
298 | end
299 |
--------------------------------------------------------------------------------
/lib/cldr/number/format/meta.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Format.Meta do
2 | @moduledoc """
3 | Describes the metadata that drives
4 | number formatting and provides functions to
5 | update the struct.
6 |
7 | ## Format definition
8 |
9 | The `:format` is a keyword list that with two
10 | elements:
11 |
12 | * `:positive` which is a keyword list for
13 | formatting a number >= zero
14 |
15 | * `:negative` which is a keyword list for
16 | formatting negative number
17 |
18 | There are two formats because we can format in
19 | an accounting style (that is, numbers surrounded
20 | by `()`) or any other arbitrary style. Typically
21 | the format for a negative number is the same as
22 | that for a positive number with a prepended
23 | minus sign.
24 |
25 | ## Localisation of number formatting
26 |
27 | Number formatting is always localised to either
28 | the currency processes locale or a locale
29 | provided as an option to `Cldr.Number.to_string/3`.
30 |
31 | The metadata is independent of the localisation
32 | process. Signs (`+`/`-`), grouping (`,`), decimal markers
33 | (`.`) and exponent signs are all localised when
34 | the number is formatted.
35 |
36 | ## Formatting directives
37 |
38 | The formats - positive and negative - are defined
39 | in the metadata struct, as a keyword list of keywords
40 | and values.
41 |
42 | The simplest formatting list might be:
43 | ```
44 | [format: _]`
45 | ```
46 | The `:format` keyword indicates
47 | that this is where the formatting number will be
48 | substituted into the format pattern.
49 |
50 | Another example would be for formatting a negative
51 | number:
52 | ```
53 | [minus: _, format: _]
54 | ```
55 | which will format with a localised minus sign
56 | followed by the formatted number. Note that the
57 | keyword value for `:minus` and `:format` are
58 | ignored.
59 |
60 | ## List of formatting keywords
61 |
62 | The following is the full list of formatting
63 | keywords which can be used to format a
64 | number. A `_` in the keyword format is
65 | used to denote `:dont_care`.
66 |
67 | * `[format: _]` inserts the formatted number
68 | exclusive of any sign
69 |
70 | * `[minus: _]` inserts a localised minus
71 | sign
72 |
73 | * `[plus: _]` inserts a localised plus sign
74 |
75 | * `[percent: _]` inserts a localised percent sign
76 |
77 | * `[permille: _]` inserts a localised permille sign
78 |
79 | * `[literal: "string"]` inserts `string` into the
80 | format without any processing
81 |
82 | * `[currency: 1..4]` inserts a localised currency
83 | symbol of the given `type`. A `:currency` must be
84 | provided as an option to `Cldr.Number.Formatter.Decimal.to_string/4`.
85 |
86 | * `[pad: "char"]` inserts the correct number of `char`s
87 | to pad the number format to the width specified by
88 | `:padding_length` in the `%Meta{}` struct. The `:pad`
89 | can be anywhere in the format list but it is most
90 | typically inserted before or after the `:format`
91 | keyword. The assumption is that the `char` is a single
92 | binary character but this is not checked.
93 |
94 | ## Currency symbol formatting
95 |
96 | Currency are localised and have four ways of being
97 | presented. The different types are defined in the
98 | `[currency: type]` keyword where `type` is an integer
99 | in the range `1..4` These types will insert
100 | into the final format:
101 |
102 | 1. The standard currency symbol like `$`,`¥` or `€`
103 | 2. The ISO currency code (like `USD` and `JPY`)
104 | 3. The localised and pluralised currency display name
105 | like "Australian dollar" or "Australian dollars"
106 | 4. The narrow currency symbol if defined for a locale
107 |
108 | """
109 | defstruct integer_digits: %{max: 0, min: 1},
110 | fractional_digits: %{max: 0, min: 0},
111 | significant_digits: %{max: 0, min: 0},
112 | exponent_digits: 0,
113 | exponent_sign: false,
114 | scientific_rounding: 0,
115 | grouping: %{
116 | fraction: %{first: 0, rest: 0},
117 | integer: %{first: 0, rest: 0}
118 | },
119 | round_nearest: 0,
120 | padding_length: 0,
121 | padding_char: " ",
122 | multiplier: 1,
123 | currency: nil,
124 | format: [
125 | positive: [format: "#"],
126 | negative: [minus: ~c"-", format: :same_as_positive]
127 | ],
128 | number: 0
129 |
130 | @typedoc "Metadata type that drives how to format a number"
131 | @type t :: %__MODULE__{
132 | :currency => nil | Cldr.Currency.t(),
133 | :exponent_digits => non_neg_integer(),
134 | :exponent_sign => boolean(),
135 | :format => [{:negative, [any(), ...]} | {:positive, [any(), ...]}, ...],
136 | :fractional_digits => %{:max => non_neg_integer(), :min => non_neg_integer()},
137 | :grouping => %{
138 | :fraction => %{:first => non_neg_integer(), :rest => non_neg_integer()},
139 | :integer => %{:first => non_neg_integer(), :rest => non_neg_integer()}
140 | },
141 | :integer_digits => %{:max => non_neg_integer(), :min => non_neg_integer()},
142 | :multiplier => non_neg_integer(),
143 | :number => non_neg_integer(),
144 | :padding_char => String.t(),
145 | :padding_length => non_neg_integer(),
146 | :round_nearest => non_neg_integer(),
147 | :scientific_rounding => non_neg_integer(),
148 | :significant_digits => %{:max => non_neg_integer(), :min => non_neg_integer()}
149 | }
150 |
151 | @doc """
152 | Returns a new number formatting metadata
153 | struct.
154 |
155 | """
156 | @spec new :: t()
157 | def new do
158 | %__MODULE__{}
159 | end
160 |
161 | @doc """
162 | Set the minimum, and optionally maximum, integer digits to
163 | format.
164 |
165 | """
166 | @spec put_integer_digits(t(), non_neg_integer, non_neg_integer) :: t()
167 | def put_integer_digits(%__MODULE__{} = meta, min, max \\ 0)
168 | when is_integer(min) and is_integer(max) do
169 | meta
170 | |> Map.put(:integer_digits, %{min: min, max: max})
171 | end
172 |
173 | @doc """
174 | Set the minimum, and optionally maximum, fractional digits to
175 | format.
176 |
177 | """
178 | @spec put_fraction_digits(t(), non_neg_integer, non_neg_integer) :: t()
179 | def put_fraction_digits(%__MODULE__{} = meta, min, max \\ 0)
180 | when is_integer(min) and is_integer(max) do
181 | meta
182 | |> Map.put(:fractional_digits, %{min: min, max: max})
183 | end
184 |
185 | @doc """
186 | Set the minimum, and optionally maximum, significant digits to
187 | format.
188 |
189 | """
190 | @spec put_significant_digits(t(), non_neg_integer, non_neg_integer) :: t()
191 | def put_significant_digits(%__MODULE__{} = meta, min, max \\ 0)
192 | when is_integer(min) and is_integer(max) do
193 | meta
194 | |> Map.put(:significant_digits, %{min: min, max: max})
195 | end
196 |
197 | @doc """
198 | Set the number of exponent digits to
199 | format.
200 |
201 | """
202 | @spec put_exponent_digits(t(), non_neg_integer) :: t()
203 | def put_exponent_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
204 | meta
205 | |> Map.put(:exponent_digits, digits)
206 | end
207 |
208 | @doc """
209 | Set whether to add the sign of the exponent to
210 | the format.
211 |
212 | """
213 | @spec put_exponent_sign(t(), boolean) :: t()
214 | def put_exponent_sign(%__MODULE__{} = meta, flag) when is_boolean(flag) do
215 | meta
216 | |> Map.put(:exponent_sign, flag)
217 | end
218 |
219 | @doc """
220 | Set the increment to which the number should
221 | be rounded.
222 |
223 | """
224 | @spec put_round_nearest_digits(t(), non_neg_integer) :: t()
225 | def put_round_nearest_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
226 | meta
227 | |> Map.put(:round_nearest, digits)
228 | end
229 |
230 | @doc """
231 | Set the number of scientific digits to which the number should
232 | be rounded.
233 |
234 | """
235 | @spec put_scientific_rounding_digits(t(), non_neg_integer) :: t()
236 | def put_scientific_rounding_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
237 | meta
238 | |> Map.put(:scientific_rounding, digits)
239 | end
240 |
241 | @spec put_padding_length(t(), non_neg_integer) :: t()
242 | def put_padding_length(%__MODULE__{} = meta, digits) when is_integer(digits) do
243 | meta
244 | |> Map.put(:padding_length, digits)
245 | end
246 |
247 | @doc """
248 | Set the padding character to be used when
249 | padding the formatted number.
250 |
251 | """
252 | @spec put_padding_char(t(), String.t()) :: t()
253 | def put_padding_char(%__MODULE__{} = meta, char) when is_binary(char) do
254 | meta
255 | |> Map.put(:padding_char, char)
256 | end
257 |
258 | @doc """
259 | Sets the multiplier for the number.
260 |
261 | Before formatting, the number is multiplied
262 | by this amount. This is useful when
263 | formatting as a percent or permille.
264 |
265 | """
266 | @spec put_multiplier(t(), non_neg_integer) :: t()
267 | def put_multiplier(%__MODULE__{} = meta, multiplier) when is_integer(multiplier) do
268 | meta
269 | |> Map.put(:multiplier, multiplier)
270 | end
271 |
272 | @doc """
273 | Sets the number of digits in a group or
274 | optionally the first group and subsequent
275 | groups for the integer part of a number.
276 |
277 | The grouping character is defined by the locale
278 | defined for the current process or supplied
279 | as the `:locale` option to `to_string/3`.
280 |
281 | """
282 | @spec put_integer_grouping(t(), non_neg_integer, non_neg_integer) :: t()
283 | def put_integer_grouping(%__MODULE__{} = meta, first, rest)
284 | when is_integer(first) and is_integer(rest) do
285 | grouping =
286 | meta
287 | |> Map.get(:grouping)
288 | |> Map.put(:integer, %{first: first, rest: rest})
289 |
290 | Map.put(meta, :grouping, grouping)
291 | end
292 |
293 | @spec put_integer_grouping(t(), non_neg_integer) :: t()
294 | def put_integer_grouping(%__MODULE__{} = meta, all) when is_integer(all) do
295 | grouping =
296 | meta
297 | |> Map.get(:grouping)
298 | |> Map.put(:integer, %{first: all, rest: all})
299 |
300 | Map.put(meta, :grouping, grouping)
301 | end
302 |
303 | @doc """
304 | Sets the number of digits in a group or
305 | optionally the first group and subsequent
306 | groups for the fractional part of a number.
307 |
308 | The grouping character is defined by the locale
309 | defined for the current process or supplied
310 | as the `:locale` option to `to_string/3`.
311 |
312 | """
313 | @spec put_fraction_grouping(t(), non_neg_integer, non_neg_integer) :: t()
314 | def put_fraction_grouping(%__MODULE__{} = meta, first, rest)
315 | when is_integer(first) and is_integer(rest) do
316 | grouping =
317 | meta
318 | |> Map.get(:grouping)
319 | |> Map.put(:fraction, %{first: first, rest: rest})
320 |
321 | Map.put(meta, :grouping, grouping)
322 | end
323 |
324 | @spec put_fraction_grouping(t(), non_neg_integer) :: t()
325 | def put_fraction_grouping(%__MODULE__{} = meta, all) when is_integer(all) do
326 | grouping =
327 | meta
328 | |> Map.get(:grouping)
329 | |> Map.put(:fraction, %{first: all, rest: all})
330 |
331 | Map.put(meta, :grouping, grouping)
332 | end
333 |
334 | @doc """
335 | Set the metadata format for the positive
336 | and negative number format.
337 |
338 | Note that this is the parsed format as a simple keyword
339 | list, not a binary representation.
340 |
341 | Its up to each formatting engine to transform its input
342 | into this form. See `Cldr.Number.Format.Meta` module
343 | documentation for the available keywords.
344 |
345 | """
346 | @spec put_format(t(), Keyword.t(), Keyword.t()) :: t()
347 | def put_format(%__MODULE__{} = meta, positive_format, negative_format) do
348 | meta
349 | |> Map.put(:format, positive: positive_format, negative: negative_format)
350 | end
351 |
352 | @spec put_format(t(), Keyword.t()) :: t()
353 | def put_format(%__MODULE__{} = meta, positive_format) do
354 | put_format(meta, positive_format, minus: ~c"-", format: :same_as_positive)
355 | end
356 | end
357 |
--------------------------------------------------------------------------------
/lib/cldr/number/formatter/currency_formatter.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Formatter.Currency do
2 | @moduledoc """
3 | Number formatter for the `:currency` `:long` format.
4 |
5 | This formatter implements formatting a currency in a long form. This
6 | is not the same as decimal formatting with a currency placeholder.
7 |
8 | To explain the difference, look at the following examples:
9 |
10 | iex> Cldr.Number.to_string 123, TestBackend.Cldr, format: :currency, currency: "USD"
11 | {:ok, "$123.00"}
12 |
13 | iex> Cldr.Number.to_string 123, TestBackend.Cldr, format: :long, currency: "USD"
14 | {:ok, "123 US dollars"}
15 |
16 | In the first example the format is defined by a decimal mask. In this example
17 | the format mask comes from:
18 |
19 | iex> {:ok, formats} = Cldr.Number.Format.all_formats_for("en", TestBackend.Cldr)
20 | ...> formats.latn.currency
21 | "¤#,##0.00"
22 |
23 | In the second example we are using a format that combines the number with
24 | a language translation of the currency name. In this example the format
25 | comes from:
26 |
27 | iex> {:ok, formats} = Cldr.Number.Format.all_formats_for("en", TestBackend.Cldr)
28 | ...> formats.latn.currency_long
29 | %{one: [0, " ", 1], other: [0, " ", 1]}
30 |
31 | Where "{0}" is replaced with the number formatted using the `:standard`
32 | decimal format and "{1} is replaced with locale-specific name of the
33 | currency adjusted for the locales plural rules."
34 |
35 | **This module is not part of the public API and is subject
36 | to change at any time.**
37 |
38 | """
39 |
40 | alias Cldr.Number.{Format, System}
41 | alias Cldr.{Substitution, Currency}
42 | alias Cldr.Number.Format.Options
43 | alias Cldr.Number.Formatter
44 |
45 | import DigitalToken, only: [is_digital_token: 1]
46 |
47 | @doc false
48 | def to_string(number, _format, _backend, _options) when is_binary(number) do
49 | {:error,
50 | {
51 | ArgumentError,
52 | "Not a number: #{inspect(number)}. Currency long formats only support number or Decimal arguments"
53 | }}
54 | end
55 |
56 | # The format :currency_long_with_symbol is a composition of :currency_long
57 | # and the default :currency format. It is a derived format, not one
58 | # defined by CLDR.
59 |
60 | def to_string(number, :currency_long_with_symbol, backend, options) do
61 | decimal_options = decimal_options(options, backend, number)
62 | decimal_format = decimal_options.format
63 |
64 | number
65 | |> Cldr.Number.to_string!(backend, long_options(options))
66 | |> Formatter.Decimal.to_string(decimal_format, backend, decimal_options)
67 | end
68 |
69 | def to_string(number, :currency_long, backend, options) do
70 | locale = options.locale
71 | number_system = System.system_name_from!(options.number_system, locale, backend)
72 | cardinal = Module.concat(backend, Number.Cardinal)
73 |
74 | if !(formats = Format.formats_for!(locale, number_system, backend).currency_long) do
75 | raise ArgumentError,
76 | message:
77 | "No :currency_long format known for " <>
78 | "locale #{inspect(locale)} and number system #{inspect(number_system)}."
79 | end
80 |
81 | options =
82 | options
83 | |> Map.put(:format, :standard)
84 | |> set_fractional_digits(options.currency, options.fractional_digits)
85 | |> Options.resolve_standard_format(backend)
86 |
87 | currency_string = currency_string(number, options.currency, cardinal, locale, backend)
88 | number_string = Cldr.Number.to_string!(number, backend, options)
89 | format = cardinal.pluralize(number, locale, formats)
90 |
91 | Substitution.substitute([number_string, currency_string], format)
92 | |> :erlang.iolist_to_binary()
93 | end
94 |
95 | defp currency_string(number, %Cldr.Currency{} = currency, cardinal, locale, _backend) do
96 | cardinal.pluralize(number, locale, currency.count)
97 | end
98 |
99 | defp currency_string(_number, currency, _cardinal, _locale, _backend)
100 | when is_digital_token(currency) do
101 | {:ok, currency_string} = DigitalToken.long_name(currency)
102 | currency_string
103 | end
104 |
105 | defp set_fractional_digits(options, %Cldr.Currency{}, nil) do
106 | Map.put(options, :fractional_digits, 0)
107 | end
108 |
109 | defp set_fractional_digits(options, _currency, _digits) do
110 | options
111 | end
112 |
113 | defp long_options(options) do
114 | options
115 | |> Map.put(:format, :decimal_long)
116 | |> Map.put(:currency, nil)
117 | end
118 |
119 | defp decimal_options(options, backend, number) do
120 | currency_format = Currency.currency_format_from_locale(options.locale)
121 | options = Map.put(options, :format, currency_format)
122 |
123 | options
124 | |> Options.resolve_standard_format(backend)
125 | |> Options.maybe_expand_currency_symbol(number)
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/lib/cldr/number/formatter/short_formatter.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Formatter.Short do
2 | @moduledoc """
3 | Formats a number according to the locale-specific `:short` formats
4 |
5 | This is best explained by some
6 | examples:
7 |
8 | iex> Cldr.Number.to_string 123, TestBackend.Cldr, format: :short
9 | {:ok, "123"}
10 |
11 | iex> Cldr.Number.to_string 1234, TestBackend.Cldr, format: :short
12 | {:ok, "1K"}
13 |
14 | iex> Cldr.Number.to_string 523456789, TestBackend.Cldr, format: :short
15 | {:ok, "523M"}
16 |
17 | iex> Cldr.Number.to_string 7234567890, TestBackend.Cldr, format: :short
18 | {:ok, "7B"}
19 |
20 | iex> Cldr.Number.to_string 7234567890, TestBackend.Cldr, format: :long
21 | {:ok, "7 billion"}
22 |
23 | These formats are compact representations however they do lose
24 | precision in the presentation in favour of human readability.
25 |
26 | Note that for a `:currency` short format the number of decimal places
27 | is retrieved from the currency definition itself. You can see the difference
28 | in the following examples:
29 |
30 | iex> Cldr.Number.to_string 1234, TestBackend.Cldr, format: :short, currency: "EUR"
31 | {:ok, "€1K"}
32 |
33 | iex> Cldr.Number.to_string 1234, TestBackend.Cldr, format: :short, currency: "EUR", fractional_digits: 2
34 | {:ok, "€1.23K"}
35 |
36 | iex> Cldr.Number.to_string 1234, TestBackend.Cldr, format: :short, currency: "JPY"
37 | {:ok, "¥1K"}
38 |
39 | **This module is not part of the public API and is subject
40 | to change at any time.**
41 |
42 | """
43 |
44 | alias Cldr.Math
45 | alias Cldr.Number.{System, Format, Formatter}
46 | alias Cldr.Locale
47 | alias Cldr.LanguageTag
48 | alias Cldr.Number.Format.Options
49 |
50 | # Notes from Unicode TR35 on formatting short formats:
51 | #
52 | # To format a number N, the greatest type less than or equal to N is
53 | # used, with the appropriate plural category. N is divided by the type, after
54 | # removing the number of zeros in the pattern, less 1. APIs supporting this
55 | # format should provide control over the number of significant or fraction
56 | # digits.
57 | #
58 | # If the value is precisely 0, or if the type is less than 1000, then the
59 | # normal number format pattern for that sort of object is supplied. For
60 | # example, formatting 1200 would result in “$1.2K”, while 990 would result in
61 | # simply “$990”.
62 | #
63 | # Thus N=12345 matches 00 K . N
64 | # is divided by 1000 (obtained from 10000 after removing "00" and restoring one
65 | # "0". The result is formatted according to the normal decimal pattern. With no
66 | # fractional digits, that yields "12 K".
67 |
68 | @spec to_string(Math.number_or_decimal(), atom(), Cldr.backend(), Options.t()) ::
69 | {:ok, String.t()} | {:error, {module(), String.t()}}
70 |
71 | def to_string(number, _style, _backend, _options) when is_binary(number) do
72 | {:error,
73 | {
74 | ArgumentError,
75 | "Not a number: #{inspect(number)}. Long and short formats only support number or Decimal arguments"
76 | }}
77 | end
78 |
79 | def to_string(number, style, backend, options) do
80 | locale = options.locale || backend.default_locale()
81 |
82 | with {:ok, locale} <- Cldr.validate_locale(locale, backend),
83 | {:ok, number_system} <- System.system_name_from(options.number_system, locale, backend) do
84 | short_format_string(number, style, locale, number_system, backend, options)
85 | end
86 | end
87 |
88 | @spec short_format_string(
89 | Math.number_or_decimal(),
90 | atom,
91 | Locale.locale_name() | LanguageTag.t(),
92 | System.system_name(),
93 | Cldr.backend(),
94 | Options.t()
95 | ) :: {:ok, String.t()} | {:error, {module(), String.t()}}
96 |
97 | defp short_format_string(number, style, locale, number_system, backend, options) do
98 | format_rules =
99 | locale
100 | |> Format.formats_for!(number_system, backend)
101 | |> Map.fetch!(style)
102 |
103 | {normalized_number, format} = choose_short_format(number, format_rules, options, backend)
104 |
105 | options =
106 | options
107 | |> digits(options.fractional_digits)
108 | |> Map.put(:format, format)
109 | |> Options.maybe_expand_currency_symbol(number)
110 |
111 | Formatter.Decimal.to_string(normalized_number, format, backend, options)
112 | end
113 |
114 | @doc """
115 | Returns the exponent that will be applied
116 | when formatting the given number as a short
117 | format.
118 |
119 | This function is primarily intended to support
120 | pluralization for compact numbers (numbers
121 | formatted with the `format: :short` option) since
122 | some languages pluralize compact numbers differently
123 | to a fully expressed number.
124 |
125 | Such rules are defined for the locale "fr" from
126 | CLDR version 38 with the intention that additional
127 | rules will be added in later versions.
128 |
129 | ## Examples
130 |
131 | iex> Cldr.Number.Formatter.Short.short_format_exponent 1234
132 | {1000, 1}
133 |
134 | iex> Cldr.Number.Formatter.Short.short_format_exponent 12345
135 | {10000, 2}
136 |
137 | iex> Cldr.Number.Formatter.Short.short_format_exponent 123456789
138 | {100000000, 3}
139 |
140 | iex> Cldr.Number.Formatter.Short.short_format_exponent 123456789, locale: "th"
141 | {100000000, 3}
142 |
143 | """
144 | def short_format_exponent(number, options \\ []) when is_list(options) do
145 | with {locale, backend} = Cldr.locale_and_backend_from(options),
146 | number_system = Keyword.get(options, :number_system, :default),
147 | {:ok, number_system} <- System.system_name_from(number_system, locale, backend),
148 | {:ok, all_formats} <- Format.formats_for(locale, number_system, backend) do
149 | formats = Map.fetch!(all_formats, :decimal_short)
150 | pluralizer = Module.concat(backend, Number.Cardinal)
151 |
152 | options =
153 | options
154 | |> Map.new()
155 | |> Map.put_new(:locale, locale)
156 | |> Map.put_new(:number_system, number_system)
157 | |> Map.put_new(:currency, nil)
158 |
159 | case get_short_format_rule(number, formats, options, backend) do
160 | [range, plural_selectors] ->
161 | normalized_number = normalise_number(number, range, plural_selectors.other)
162 | plural_key = pluralization_key(normalized_number, options)
163 |
164 | [_format, number_of_zeros] =
165 | pluralizer.pluralize(plural_key, options.locale, plural_selectors)
166 |
167 | {range, number_of_zeros}
168 |
169 | {number, _format} ->
170 | {number, 0}
171 | end
172 | end
173 | end
174 |
175 | # For short formats the fractional digits should be 0 unless otherwise specified,
176 | # even for currencies
177 | defp digits(options, nil) do
178 | Map.put(options, :fractional_digits, 0)
179 | end
180 |
181 | defp digits(options, _digits) do
182 | options
183 | end
184 |
185 | defp choose_short_format(number, format_rules, options, backend)
186 | when is_number(number) and number < 0 do
187 | {number, format} = choose_short_format(abs(number), format_rules, options, backend)
188 | {number * -1, format}
189 | end
190 |
191 | defp choose_short_format(%Decimal{sign: -1 = sign} = number, format_rules, options, backend) do
192 | {normalized_number, format} =
193 | choose_short_format(Decimal.abs(number), format_rules, options, backend)
194 |
195 | {Decimal.mult(normalized_number, sign), format}
196 | end
197 |
198 | defp choose_short_format(number, format_rules, options, backend) do
199 | pluralizer = Module.concat(backend, Number.Cardinal)
200 |
201 | case get_short_format_rule(number, format_rules, options, backend) do
202 | # Its a short format
203 | [range, plural_selectors] ->
204 | normalized_number = normalise_number(number, range, plural_selectors.other)
205 | plural_key = pluralization_key(normalized_number, options)
206 |
207 | [format, _number_of_zeros] =
208 | pluralizer.pluralize(plural_key, options.locale, plural_selectors)
209 |
210 | {normalized_number, format}
211 |
212 | # Its a standard format
213 | {number, format} ->
214 | {number, format}
215 | end
216 | end
217 |
218 | defp get_short_format_rule(number, _format_rules, options, backend)
219 | when is_number(number) and number < 1000 do
220 | format =
221 | options.locale
222 | |> Format.formats_for!(options.number_system, backend)
223 | |> Map.get(standard_or_currency(options))
224 |
225 | {number, format}
226 | end
227 |
228 | defp get_short_format_rule(number, format_rules, options, backend) when is_number(number) do
229 | format_rules
230 | |> Enum.filter(fn [range, _rules] -> range <= number end)
231 | |> Enum.reverse()
232 | |> hd
233 | |> maybe_get_default_format(number, options, backend)
234 | end
235 |
236 | defp get_short_format_rule(%Decimal{} = number, format_rules, options, backend) do
237 | rule =
238 | number
239 | |> Decimal.round(0, :floor)
240 | |> Decimal.to_integer()
241 | |> get_short_format_rule(format_rules, options, backend)
242 |
243 | case rule do
244 | {_ignore, format} -> {number, format}
245 | rule -> rule
246 | end
247 | end
248 |
249 | defp maybe_get_default_format([_range, %{other: ["0", _]}], number, options, backend) do
250 | {_, format} = get_short_format_rule(0, [], options, backend)
251 | {number, format}
252 | end
253 |
254 | defp maybe_get_default_format(rule, _number, _options, _backend) do
255 | rule
256 | end
257 |
258 | defp standard_or_currency(options) do
259 | if options.currency do
260 | :currency
261 | else
262 | :standard
263 | end
264 | end
265 |
266 | @one_thousand Decimal.new(1000)
267 | defp normalise_number(%Decimal{} = number, range, number_of_zeros) do
268 | if Cldr.Decimal.compare(number, @one_thousand) == :lt do
269 | number
270 | else
271 | Decimal.div(number, Decimal.new(adjustment(range, number_of_zeros)))
272 | end
273 | end
274 |
275 | defp normalise_number(number, _range, _number_of_zeros) when number < 1000 do
276 | number
277 | end
278 |
279 | defp normalise_number(number, _range, ["0", _number_of_zeros]) do
280 | number
281 | end
282 |
283 | defp normalise_number(number, range, [_format, number_of_zeros]) do
284 | number / adjustment(range, number_of_zeros)
285 | end
286 |
287 | # TODO: We can precompute these at compile time which would
288 | # save this lookup
289 | defp adjustment(range, number_of_zeros) when is_integer(number_of_zeros) do
290 | (range / Math.power_of_10(number_of_zeros - 1))
291 | |> trunc
292 | end
293 |
294 | defp adjustment(range, [_, number_of_zeros]) when is_integer(number_of_zeros) do
295 | adjustment(range, number_of_zeros)
296 | end
297 |
298 | # The pluralization key has to consider when there is an
299 | # exact match and when the number would be rounded up. When
300 | # rounded up it also has to not be an exact match.
301 | defp pluralization_key(number, options) when is_number(number) do
302 | rounding_mode = Map.get_lazy(options, :rounding_mode, &Cldr.Math.default_rounding_mode/0)
303 |
304 | if (rounded = Cldr.Math.round(number, 0, rounding_mode)) <= number do
305 | # Rounded number <= number means that the
306 | # pluralization key is the same integer part
307 | # so no issue
308 | number
309 | else
310 | # The rounded number is greater than the normalized
311 | # number so the plural key is different but not exactly
312 | # equal so we add an offset so pluralization works
313 | # correctly (we don't want to trigger an exact match;
314 | # although this relies on exact matches always being integers
315 | # which as of CLDR39 they are).
316 | rounded + 0.1
317 | end
318 | end
319 |
320 | defp pluralization_key(%Decimal{} = number, options) do
321 | number
322 | |> Decimal.to_float()
323 | |> pluralization_key(options)
324 | end
325 | end
326 |
--------------------------------------------------------------------------------
/lib/cldr/number/rbnf.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Rbnf do
2 | @moduledoc """
3 | Functions to implement Rules Based Number Formatting (rbnf)
4 |
5 | During compilation RBNF rules are extracted and generated
6 | as function bodies by `Cldr.Rbnf.Ordinal`, `Cldr.Rbnf.Cardinal`
7 | and `Cldr.Rbnf.NumberSystem`.
8 |
9 | The functions in this module would not normally be of common
10 | use outside of supporting the compilation phase.
11 | """
12 |
13 | alias Cldr.LanguageTag
14 |
15 | @doc """
16 | Returns the list of locales that that have RBNF defined
17 |
18 | This list is the set of known locales for which
19 | there are rbnf rules defined.
20 |
21 | Delegates to `Cldr.known_rbnf_locale_names/1`
22 |
23 | """
24 | defdelegate known_locale_names(backend), to: Cldr, as: :known_rbnf_locale_names
25 |
26 | @categories [:NumberSystem, :Spellout, :Ordinal]
27 |
28 | @doc """
29 | Returns the list of RBNF rules for a locale.
30 |
31 | A rule name can be used as the `:format` parameter
32 | in `Cldr.Number.to_string/3`.
33 |
34 | ## Arguments
35 |
36 | * `locale` is any `Cldr.LanguageTag.t()`
37 |
38 | ## Returns
39 |
40 | * `{:ok, [list_of_rule_names_as_atoms]}` or
41 |
42 | * `{:error, {exception, reason}}`
43 |
44 | ## Examples
45 |
46 | iex> Cldr.Rbnf.rule_names_for_locale "zh"
47 | {:ok,
48 | [:spellout_cardinal_alternate2, :spellout_ordinal, :spellout_cardinal,
49 | :spellout_cardinal_financial, :spellout_numbering, :spellout_numbering_days,
50 | :spellout_numbering_year, :digits_ordinal]}
51 |
52 | iex> Cldr.Rbnf.rule_names_for_locale "fp"
53 | {:error, {Cldr.InvalidLanguageError, "The language \"fp\" is invalid"}}
54 |
55 | """
56 | @spec rule_names_for_locale(Cldr.LanguageTag.t()) ::
57 | {:ok, list(atom())} | {:error, {module(), String.t()}}
58 |
59 | def rule_names_for_locale(%LanguageTag{rbnf_locale_name: nil} = language_tag) do
60 | {:error, rbnf_locale_error(language_tag)}
61 | end
62 |
63 | def rule_names_for_locale(%LanguageTag{rbnf_locale_name: rbnf_locale_name, backend: backend}) do
64 | rule_names =
65 | Enum.flat_map(@categories, fn category ->
66 | rbnf_module = Module.concat([backend, :Rbnf, category])
67 | rule_names = rbnf_module.rule_sets(rbnf_locale_name)
68 | if rule_names, do: rule_names, else: []
69 | end)
70 |
71 | {:ok, rule_names}
72 | end
73 |
74 | def rule_names_for_locale(locale_name, backend \\ Cldr.default_backend!())
75 | when is_binary(locale_name) or is_atom(locale_name) do
76 | with {:ok, locale} <- Cldr.Locale.canonical_language_tag(locale_name, backend) do
77 | rule_names_for_locale(locale)
78 | end
79 | end
80 |
81 | @doc """
82 | Returns the list of RBNF rules for a locale.
83 |
84 | A rule name can be used as the `:format` parameter
85 | in `Cldr.Number.to_string/3`.
86 |
87 | ## Arguments
88 |
89 | * `locale` is any `Cldr.LanguageTag.t()`
90 |
91 | ## Returns
92 |
93 | * `[list_of_rule_names_as_atoms]`, or
94 |
95 | * raises an exception
96 |
97 | ## Examples
98 |
99 | iex> Cldr.Rbnf.rule_names_for_locale! "zh"
100 | [:spellout_cardinal_alternate2, :spellout_ordinal, :spellout_cardinal,
101 | :spellout_cardinal_financial, :spellout_numbering, :spellout_numbering_days,
102 | :spellout_numbering_year, :digits_ordinal]
103 |
104 | """
105 | @spec rule_names_for_locale!(Cldr.LanguageTag.t()) :: list(atom()) | no_return()
106 |
107 | def rule_names_for_locale!(locale) do
108 | case rule_names_for_locale(locale) do
109 | {:ok, rule_names} -> rule_names
110 | {:error, {exception, reason}} -> raise exception, reason
111 | end
112 | end
113 |
114 | @doc """
115 | Returns {:ok, rbnf_rules} for a `locale` or `{:error, {Cldr.NoRbnf, info}}`
116 |
117 | * `locale` is any `t:Cldr.LanguageTag`
118 |
119 | This function reads the raw locale definition and therefore
120 | should *not* be called at runtime.
121 |
122 | """
123 | @spec for_locale(LanguageTag.t()) ::
124 | {:ok, map()} | {:error, {module(), String.t()}}
125 |
126 | def for_locale(%LanguageTag{rbnf_locale_name: nil} = language_tag) do
127 | {:error, rbnf_locale_error(language_tag)}
128 | end
129 |
130 | def for_locale(%LanguageTag{rbnf_locale_name: rbnf_locale_name, backend: backend}) do
131 | rbnf_data =
132 | rbnf_locale_name
133 | |> Cldr.Locale.Loader.get_locale(backend)
134 | |> Map.get(:rbnf)
135 |
136 | {:ok, rbnf_data}
137 | end
138 |
139 | @doc """
140 | Returns {:ok, rbnf_rules} for a `locale` or `{:error, {Cldr.NoRbnf, info}}`
141 |
142 | * `locale` is any locale name returned by `Cldr.Rbnf.known_locale_names/1`
143 |
144 | * `backend` is any module that includes `use Cldr` and therefore
145 | is a `Cldr` backend module
146 |
147 | """
148 | @spec for_locale(Cldr.Locale.locale_name() | LanguageTag.t(), Cldr.backend()) ::
149 | {:ok, map()} | {:error, {module(), String.t()}}
150 |
151 | def for_locale(locale, backend) do
152 | with {:ok, language_tag} <- Cldr.Locale.canonical_language_tag(locale, backend) do
153 | for_locale(language_tag)
154 | end
155 | end
156 |
157 | @doc """
158 | Returns rbnf_rules for a `locale` or raises an exception if
159 | there are no rules.
160 |
161 | * `locale` is any `Cldr.LanguageTag`
162 |
163 | * `backend` is any module that includes `use Cldr` and therefore
164 | is a `Cldr` backend module
165 |
166 | """
167 | def for_locale!(%LanguageTag{} = locale) do
168 | case for_locale(locale) do
169 | {:ok, rules} -> rules
170 | {:error, {exception, reason}} -> raise exception, reason
171 | end
172 | end
173 |
174 | @doc """
175 | Returns rbnf_rules for a `locale` and `backend` or raises an exception if
176 | there are no rules.
177 |
178 | * `locale` is any locale name returned by `Cldr.Rbnf.known_locale_names/1`
179 |
180 | * `backend` is any module that includes `use Cldr` and therefore
181 | is a `Cldr` backend module
182 |
183 | """
184 | def for_locale!(locale, backend) when is_atom(backend) do
185 | case for_locale(locale, backend) do
186 | {:ok, rules} -> rules
187 | {:error, {exception, reason}} -> raise exception, reason
188 | end
189 | end
190 |
191 | @doc false
192 | def categories_for_locale!(%LanguageTag{} = locale) do
193 | Enum.reduce(@categories, [], fn category, acc ->
194 | rbnf_module = Module.concat([locale.backend, :Rbnf, category])
195 |
196 | case rbnf_module.rule_sets(locale) do
197 | nil -> acc
198 | _rules -> [category | acc]
199 | end
200 | end)
201 | end
202 |
203 | # Returns a map that merges all rules by the primary dimension of
204 | # RuleGroup, within which rbnf rules are keyed by locale.
205 | #
206 | # This function is primarily intended to support compile-time generation
207 | # of functions to process rbnf rules.
208 | @doc false
209 | @spec for_all_locales(Cldr.backend()) :: %{}
210 | def for_all_locales(backend) do
211 | config = Module.get_attribute(backend, :config)
212 | known_rbnf_locale_names = Cldr.Locale.Loader.known_rbnf_locale_names(config)
213 |
214 | Enum.map(known_rbnf_locale_names, fn locale_name ->
215 | locale =
216 | locale_name
217 | |> Cldr.Locale.Loader.get_locale(config)
218 | |> Map.get(:rbnf)
219 |
220 | Enum.map(locale, fn {group, sets} ->
221 | {group, %{locale_name => sets}}
222 | end)
223 | |> Enum.into(%{})
224 | end)
225 | |> Cldr.Map.merge_map_list()
226 | end
227 |
228 | def rbnf_locale_error(%LanguageTag{} = locale) do
229 | {Cldr.Rbnf.NotAvailable, "RBNF is not available for locale #{inspect(locale)}"}
230 | end
231 |
232 | def rbnf_rule_error(%LanguageTag{} = locale, format) do
233 | {
234 | Cldr.Rbnf.NoRule,
235 | "RBNF rule #{inspect(format)} is unknown to locale #{inspect(locale)}"
236 | }
237 | end
238 |
239 | # Function names need to start with a letter. At least one
240 | # rule in CLDR does not (2d_year). We prepend an "r" to the
241 | # rule. This is also done in rbnf_parse.yrl so the strategies
242 | # need to match.
243 |
244 | @doc false
245 | def force_valid_function_name(rule_group) do
246 | function = to_string(rule_group)
247 |
248 | case function do
249 | <> when digit in ?0..?9 ->
250 | "r" <> function
251 |
252 | _other ->
253 | function
254 | end
255 | |> String.to_atom()
256 | end
257 |
258 | if Mix.env() == :test do
259 | # Returns all the rules in rbnf without any tagging for rulegroup or set.
260 | # This is helpful for testing only.
261 | @doc false
262 | def all_rules(backend) do
263 | # Get sets from groups
264 | # Get rules from set
265 | # Get the list of rules
266 | known_locale_names(backend)
267 | |> Enum.map(&Cldr.Locale.new!(&1, backend))
268 | |> Enum.map(&for_locale!(&1))
269 | |> Enum.flat_map(&Map.values/1)
270 | |> Enum.flat_map(&Map.values/1)
271 | |> Enum.flat_map(& &1.rules)
272 | end
273 |
274 | # Returns a list of unique rule definitions. Used for testing.
275 | @doc false
276 | def all_rule_definitions(backend) do
277 | all_rules(backend)
278 | |> Enum.map(& &1.definition)
279 | |> Enum.uniq()
280 | end
281 | end
282 | end
283 |
--------------------------------------------------------------------------------
/lib/cldr/number/rbnf/rule.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Rbnf.Rule do
2 | @moduledoc """
3 | Tokenizer and Parser for RBNF rules.
4 |
5 | """
6 | defstruct [:base_value, :radix, :definition, :range, :divisor]
7 | alias Cldr.Rbnf.Rule
8 |
9 | @doc """
10 | Scan and tokenize rule definition
11 |
12 | Using a leex lexer, tokenize a rule definition
13 | """
14 | def tokenize(definition) when is_binary(definition) do
15 | definition
16 | |> String.trim_leading("'")
17 | |> String.to_charlist()
18 | |> :rbnf_lexer.string()
19 | end
20 |
21 | def tokenize(%Rule{definition: definition} = _rule) do
22 | tokenize(definition)
23 | end
24 |
25 | @doc """
26 | Parse an RBNF rule definition
27 |
28 | Returns a list of rule subparts that can then be used for
29 | further processing or for turning into an AST for execution.
30 | """
31 | def parse(definition) when is_binary(definition) do
32 | {:ok, tokens, _end_line} = tokenize(definition)
33 | parse(tokens)
34 | end
35 |
36 | def parse([]) do
37 | {:ok, []}
38 | end
39 |
40 | def parse(tokens) when is_list(tokens) do
41 | tokens
42 | |> :rbnf_parser.parse()
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/cldr/number/string.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.String do
2 | @moduledoc false
3 |
4 | @doc """
5 | Returns a regex which matches all latin1 characters.
6 |
7 | """
8 | @latin1 "([\\x00-\\x7F])"
9 | def latin1 do
10 | ~r/#{@latin1}/
11 | end
12 |
13 | @doc """
14 | Returns a regex which matches all non-latin1 characters.
15 |
16 | """
17 | @not_latin1 "([^\\x00-\\x7F])"
18 | def not_latin1 do
19 | ~r/#{@not_latin1}/
20 | end
21 |
22 | @doc """
23 | Replaces characters with a string hex representation.
24 | """
25 | def hex_string(string) do
26 | String.to_charlist(string)
27 | |> Enum.map(&("\\x" <> Integer.to_string(&1)))
28 | |> Enum.join()
29 | end
30 |
31 | @doc """
32 | Pad a a string (representing a number) with leading "0"'s to the
33 | specified length.
34 |
35 | ## Options
36 |
37 | * `number` is a string representation of a number.
38 |
39 | * `count` is the final length required of the string.
40 |
41 | """
42 | @spec pad_leading_zeros(String.t(), integer) :: String.t()
43 | def pad_leading_zeros(number_string, count) when count <= 0 do
44 | number_string
45 | end
46 |
47 | def pad_leading_zeros(number_string, count) do
48 | :binary.copy("0", count - byte_size(number_string)) <> number_string
49 | end
50 |
51 | @doc """
52 | Pad a a string (representing a number) with trailing "0"'s to the
53 | specified length.
54 |
55 | ## Options
56 |
57 | * `number` is a string representation of a number.
58 |
59 | * `count` is the final length required of the string.
60 |
61 | """
62 | @spec pad_trailing_zeros(String.t(), integer) :: String.t()
63 | def pad_trailing_zeros(number_string, count) when count <= 0 do
64 | number_string
65 | end
66 |
67 | def pad_trailing_zeros(number_string, count) do
68 | number_string <> :binary.copy("0", count - byte_size(number_string))
69 | end
70 |
71 | @doc """
72 | Split a string up into fixed size chunks.
73 |
74 | Returns a list of strings the size of `size` plus potentially
75 | one more chunk at the end that is the remainder of the string
76 | after chunking.
77 |
78 | ## Examples
79 |
80 | iex> Cldr.Number.String.chunk_string("This is a string", 3)
81 | ["Thi", "s i", "s a", " st", "rin", "g"]
82 |
83 | iex> Cldr.Number.String.chunk_string("1234", 4)
84 | ["1234"]
85 |
86 | iex> Cldr.Number.String.chunk_string("1234", 3)
87 | ["123","4"]
88 |
89 | iex> Cldr.Number.String.chunk_string("1234", 3, :reverse)
90 | ["1", "234"]
91 |
92 | """
93 | @spec chunk_string(String.t(), integer, :forward | :reverse) :: [String.t()]
94 | def chunk_string(string, size, direction \\ :forward)
95 |
96 | def chunk_string(string, 0, _direction) do
97 | [string]
98 | end
99 |
100 | def chunk_string("", _size, _) do
101 | [""]
102 | end
103 |
104 | if Version.compare(System.version(), "1.6.0") in [:gt, :eq] do
105 | def chunk_string(string, size, :forward) do
106 | string
107 | |> String.to_charlist()
108 | |> Enum.chunk_every(size, size, [])
109 | |> Enum.map(&List.to_string/1)
110 | end
111 | else
112 | def chunk_string(string, size, :forward) do
113 | string
114 | |> String.to_charlist()
115 | |> Enum.chunk(size, size, [])
116 | |> Enum.map(&List.to_string/1)
117 | end
118 | end
119 |
120 | def chunk_string(string, size, :reverse) do
121 | len = String.length(string)
122 | remainder = rem(len, size)
123 |
124 | if remainder > 0 do
125 | {head, last} = String.split_at(string, remainder)
126 | [head] ++ do_chunk_string(last, size)
127 | else
128 | do_chunk_string(string, size)
129 | end
130 | end
131 |
132 | defp do_chunk_string("", _size) do
133 | []
134 | end
135 |
136 | defp do_chunk_string(string, size) do
137 | {chunk, rest} = String.split_at(string, size)
138 | [chunk] ++ do_chunk_string(rest, size)
139 | end
140 | end
141 |
--------------------------------------------------------------------------------
/lib/cldr/number/symbol.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Symbol do
2 | @moduledoc """
3 | Functions to manage the symbol definitions for a locale and
4 | number system.
5 |
6 | """
7 |
8 | alias Cldr.Locale
9 | alias Cldr.LanguageTag
10 | alias Cldr.Number.System
11 |
12 | defstruct [
13 | :decimal,
14 | :group,
15 | :exponential,
16 | :infinity,
17 | :list,
18 | :minus_sign,
19 | :nan,
20 | :per_mille,
21 | :percent_sign,
22 | :plus_sign,
23 | :superscripting_exponent,
24 | :time_separator
25 | ]
26 |
27 | @type t :: %__MODULE__{
28 | decimal: String.t(),
29 | group: String.t(),
30 | exponential: String.t(),
31 | infinity: String.t(),
32 | list: String.t(),
33 | minus_sign: String.t(),
34 | nan: String.t(),
35 | per_mille: String.t(),
36 | percent_sign: String.t(),
37 | plus_sign: String.t(),
38 | superscripting_exponent: String.t(),
39 | time_separator: String.t()
40 | }
41 |
42 | @doc """
43 | Returns a map of `Cldr.Number.Symbol.t` structs of the number symbols for each
44 | of the number systems of a locale.
45 |
46 | ## Arguments
47 |
48 | * `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
49 | or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`. The
50 | default is `Cldr.get_locale/1`.
51 |
52 | * `backend` is any module that includes `use Cldr` and therefore
53 | is a `Cldr` backend module
54 |
55 | ## Example:
56 |
57 | iex> Cldr.Number.Symbol.number_symbols_for("th", TestBackend.Cldr)
58 | {
59 | :ok,
60 | %{
61 | latn: %Cldr.Number.Symbol{
62 | decimal: %{standard: "."},
63 | exponential: "E",
64 | group: %{standard: ","},
65 | infinity: "∞",
66 | list: ";",
67 | minus_sign: "-",
68 | nan: "NaN",
69 | per_mille: "‰",
70 | percent_sign: "%",
71 | plus_sign: "+",
72 | superscripting_exponent: "×",
73 | time_separator: ":"
74 | },
75 | thai: %Cldr.Number.Symbol{
76 | decimal: %{standard: "."},
77 | exponential: "E",
78 | group: %{standard: ","},
79 | infinity: "∞",
80 | list: ";",
81 | minus_sign: "-",
82 | nan: "NaN",
83 | per_mille: "‰",
84 | percent_sign: "%",
85 | plus_sign: "+",
86 | superscripting_exponent: "×",
87 | time_separator: ":"
88 | }
89 | }
90 | }
91 |
92 | """
93 | @spec number_symbols_for(Locale.locale_reference(), Cldr.backend()) ::
94 | {:ok, map()} | {:error, {module(), String.t()}}
95 |
96 | def number_symbols_for(locale, backend) do
97 | Module.concat(backend, Number.Symbol).number_symbols_for(locale)
98 | end
99 |
100 | @doc """
101 | Returns the number symbols for a specific locale and number system.
102 |
103 | ## Options
104 |
105 | * `locale` is any valid locale name returned by `Cldr.known_locale_names/1`
106 | or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2`. The
107 | default is `Cldr.get_locale/1`.
108 |
109 | * `number_system` is any number system name returned by
110 | `Cldr.known_number_systems/0` or a number system type
111 | returned by `Cldr.known_number_system_types/0`
112 |
113 | * `backend` is any module that includes `use Cldr` and therefore
114 | is a `Cldr` backend module
115 |
116 | ## Example
117 |
118 | iex> Cldr.Number.Symbol.number_symbols_for("th", "thai", TestBackend.Cldr)
119 | {
120 | :ok,
121 | %Cldr.Number.Symbol{
122 | decimal: %{standard: "."},
123 | exponential: "E",
124 | group: %{standard: ","},
125 | infinity: "∞",
126 | list: ";",
127 | minus_sign: "-",
128 | nan: "NaN",
129 | per_mille: "‰",
130 | percent_sign: "%",
131 | plus_sign: "+",
132 | superscripting_exponent: "×",
133 | time_separator: ":"
134 | }
135 | }
136 |
137 | """
138 | @spec number_symbols_for(
139 | Locale.locale_reference(),
140 | System.system_name(),
141 | Cldr.backend()
142 | ) :: {:ok, map()} | {:error, {Cldr.NoNumberSymbols, String.t()}}
143 |
144 | def number_symbols_for(%LanguageTag{} = locale, number_system, backend) do
145 | with {:ok, system_name} <-
146 | Cldr.Number.System.system_name_from(number_system, locale, backend),
147 | {:ok, symbols} <- number_symbols_for(locale, backend) do
148 | symbols
149 | |> Map.get(system_name)
150 | |> symbols_return(locale, number_system)
151 | end
152 | end
153 |
154 | def number_symbols_for(locale_name, number_system, backend) do
155 | with {:ok, locale} <- Cldr.validate_locale(locale_name, backend) do
156 | number_symbols_for(locale, number_system, backend)
157 | end
158 | end
159 |
160 | @doc """
161 | Returns a list of all decimal symbols defined
162 | by the locales configured in the given backend as
163 | a list.
164 |
165 | ## Arguments
166 |
167 | * `backend` is any module that includes `use Cldr` and therefore
168 | is a `Cldr` backend module
169 |
170 | """
171 | def all_decimal_symbols(backend) do
172 | Module.concat(backend, Number.Symbol).all_decimal_symbols
173 | end
174 |
175 | @doc """
176 | Returns a list of all grouping symbols defined
177 | by the locales configured in the given backend as
178 | a list.
179 |
180 | ## Arguments
181 |
182 | * `backend` is any module that includes `use Cldr` and therefore
183 | is a `Cldr` backend module
184 |
185 | """
186 | def all_grouping_symbols(backend) do
187 | Module.concat(backend, Number.Symbol).all_grouping_symbols
188 | end
189 |
190 | @doc """
191 | Returns a list of all decimal symbols defined
192 | by the locales configured in the given backend as
193 | a string.
194 |
195 | This string can be used as a character class
196 | when builing a regular expression.
197 |
198 | ## Arguments
199 |
200 | * `backend` is any module that includes `use Cldr` and therefore
201 | is a `Cldr` backend module
202 |
203 | """
204 | def all_decimal_symbols_class(backend) do
205 | Module.concat(backend, Number.Symbol).all_decimal_symbols_class
206 | end
207 |
208 | @doc """
209 | Returns a list of all grouping symbols defined
210 | by the locales configured in the given backend as
211 | a string.
212 |
213 | This string can be used as a character class
214 | when builing a regular expression.
215 |
216 | ## Arguments
217 |
218 | * `backend` is any module that includes `use Cldr` and therefore
219 | is a `Cldr` backend module
220 |
221 | """
222 | def all_grouping_symbols_class(backend) do
223 | Module.concat(backend, Number.Symbol).all_grouping_symbols_class
224 | end
225 |
226 | @doc false
227 | def symbols_return(nil, locale, number_system) do
228 | {
229 | :error,
230 | {
231 | Cldr.NoNumberSymbols,
232 | "The locale #{inspect(locale)} does not have " <>
233 | "any symbols for number system #{inspect(number_system)}"
234 | }
235 | }
236 | end
237 |
238 | @doc false
239 | def symbols_return(symbols, _locale, _number_system) do
240 | {:ok, symbols}
241 | end
242 | end
243 |
--------------------------------------------------------------------------------
/lib/cldr/number/transliterate.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Transliterate do
2 | @moduledoc """
3 | Transliteration for digits and separators.
4 |
5 | Transliterating a string is an expensive business. First the string has to
6 | be exploded into its component graphemes. Then for each grapheme we have
7 | to map to the equivalent in the other `{locale, number_system}`. Then we
8 | have to reassemble the string.
9 |
10 | Effort is made to short circuit where possible. Transliteration is not
11 | required for any `{locale, number_system}` that is the same as `{"en",
12 | "latn"}` since the implementation uses this combination for the placeholders during
13 | formatting already. When short circuiting is possible (typically the en-*
14 | locales with "latn" number_system - the total number of short circuited
15 | locales is 211 of the 537 in CLDR) the overall number formatting is twice as
16 | fast than when formal transliteration is required.
17 |
18 | ### Configuring precompilation of digit transliterations
19 |
20 | This module includes `Cldr.Number.Transliterate.transliterate_digits/3` which transliterates
21 | digits between number systems. For example from :arabic to :latn. Since generating a
22 | transliteration map is slow, pairs of transliterations can be configured so that the
23 | transliteration map is created at compile time and therefore speeding up transliteration at
24 | run time.
25 |
26 | To configure these transliteration pairs, add the following to your backend configuration:
27 |
28 | defmodule MyApp.Cldr do
29 | use Cldr,
30 | locale: ["en", "fr", "th"],
31 | default_locale: "en",
32 | precompile_transliterations: [{:latn, :thai}, {:arab, :thai}]
33 | end
34 |
35 | Where each tuple in the list configures one transliteration map. In this example, two maps are
36 | configured: from :latn to :thai and from :arab to :thai.
37 |
38 | A list of configurable number systems is returned by `Cldr.Number.System.numeric_systems/0`.
39 |
40 | If a transliteration is requested between two number pairs that have not been configured for
41 | precompilation, a warning is logged.
42 |
43 | """
44 |
45 | require Logger
46 | alias Cldr.Number.System
47 |
48 | @doc """
49 | Transliterates from latin digits to another number system's digits.
50 |
51 | Transliterates the latin digits 0..9 to their equivalents in
52 | another number system. Also transliterates the decimal and grouping
53 | separators as well as the plus, minus and exponent symbols. Any other character
54 | in the string will be returned "as is".
55 |
56 | * `sequence` is the string to be transliterated.
57 |
58 | * `locale` is any known locale, defaulting to `Cldr.get_locale/0`.
59 |
60 | * `number_system` is any known number system. If expressed as a `string` it
61 | is the actual name of a known number system. If epressed as an `atom` it is
62 | used as a key to look up a number system for the locale (the usual keys are
63 | `:default` and `:native` but :traditional and :finance are also part of the
64 | standard). See `Cldr.Number.System.number_systems_for/2` for a locale to
65 | see what number system types are defined. The default is `:default`.
66 |
67 | For available number systems see `Cldr.Number.System.number_systems/0`
68 | and `Cldr.Number.System.number_systems_for/2`. Also see
69 | `Cldr.Number.Symbol.number_symbols_for/2`.
70 |
71 |
72 | ## Examples
73 |
74 | iex> Cldr.Number.Transliterate.transliterate("123556", "en", :default, TestBackend.Cldr)
75 | "123556"
76 |
77 | iex> Cldr.Number.Transliterate.transliterate("123,556.000", "fr", :default, TestBackend.Cldr)
78 | "123 556,000"
79 |
80 | iex> Cldr.Number.Transliterate.transliterate("123556", "th", :default, TestBackend.Cldr)
81 | "123556"
82 |
83 | iex> Cldr.Number.Transliterate.transliterate("123556", "th", "thai", TestBackend.Cldr)
84 | "๑๒๓๕๕๖"
85 |
86 | iex> Cldr.Number.Transliterate.transliterate("123556", "th", :native, TestBackend.Cldr)
87 | "๑๒๓๕๕๖"
88 |
89 | iex> Cldr.Number.Transliterate.transliterate("Some number is: 123556", "th", "thai", TestBackend.Cldr)
90 | "Some number is: ๑๒๓๕๕๖"
91 |
92 | """
93 | def transliterate(sequence, locale, number_system, backend, options \\ []) do
94 | backend = Module.concat(backend, Number.Transliterate)
95 | backend.transliterate(sequence, locale, number_system, options)
96 | end
97 |
98 | def transliterate_digits(digits, from_system, from_system) do
99 | digits
100 | end
101 |
102 | def transliterate_digits(digits, from_system, to_system) when is_binary(digits) do
103 | with {:ok, from} <- System.number_system_digits(from_system),
104 | {:ok, to} <- System.number_system_digits(to_system) do
105 | log_warning(
106 | "Transliteration from number system #{inspect(from_system)} to " <>
107 | "#{inspect(to_system)} requires dynamic generation of a transliteration map for " <>
108 | "each function call which is slow. Please consider configuring this transliteration pair. " <>
109 | "See `Cldr.Number.Transliteration` for further information."
110 | )
111 |
112 | map = System.generate_transliteration_map(from, to)
113 | do_transliterate_digits(digits, map)
114 | else
115 | {:error, message} -> {:error, message}
116 | end
117 | end
118 |
119 | if macro_exported?(Logger, :warning, 2) do
120 | defp log_warning(message) do
121 | Logger.warning(fn -> message end)
122 | end
123 | else
124 | defp log_warning(message) do
125 | Logger.warn(message)
126 | end
127 | end
128 |
129 | defp do_transliterate_digits(digits, map) do
130 | digits
131 | |> String.graphemes()
132 | |> Enum.map(&Map.get(map, &1, &1))
133 | |> Enum.join()
134 | end
135 | end
136 |
--------------------------------------------------------------------------------
/lib/cldr/protocol/cldr_chars.ex:
--------------------------------------------------------------------------------
1 | defimpl Cldr.Chars, for: [Float, Integer, Decimal] do
2 | def to_string(number) do
3 | locale = Cldr.get_locale()
4 | Cldr.Number.to_string!(number, locale.backend, locale: locale)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elixir-cldr/cldr_numbers/1fb2f82370c262f83dc348a9781211926ee97fa9/logo.png
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Numbers.Mixfile do
2 | @moduledoc false
3 |
4 | use Mix.Project
5 |
6 | @version "2.35.1"
7 |
8 | def project do
9 | [
10 | app: :ex_cldr_numbers,
11 | version: @version,
12 | elixir: "~> 1.12",
13 | name: "Cldr Numbers",
14 | description: description(),
15 | source_url: "https://github.com/elixir-cldr/cldr_numbers",
16 | docs: docs(),
17 | build_embedded: Mix.env() == :prod,
18 | start_permanent: Mix.env() == :prod,
19 | elixirc_paths: elixirc_paths(Mix.env()),
20 | deps: deps(),
21 | package: package(),
22 | compilers: [:leex, :yecc] ++ Mix.compilers(),
23 | dialyzer: [
24 | ignore_warnings: ".dialyzer_ignore_warnings",
25 | plt_add_apps: ~w(inets jason mix)a,
26 | flags: [
27 | :error_handling,
28 | :unknown,
29 | :underspecs,
30 | :extra_return,
31 | :missing_return
32 | ]
33 | ]
34 | ]
35 | end
36 |
37 | def application do
38 | [
39 | extra_applications: [:logger]
40 | ]
41 | end
42 |
43 | defp description do
44 | """
45 | Number and currency localization and formatting functions for the Common Locale Data
46 | Repository (CLDR).
47 | """
48 | end
49 |
50 | defp deps do
51 | [
52 | {:ex_cldr, "~> 2.42"},
53 | {:ex_cldr_currencies, "~> 2.16"},
54 |
55 | {:digital_token, "~> 0.3 or ~> 1.0"},
56 | {:decimal, "~> 1.6 or ~> 2.0"},
57 | {:jason, "~> 1.0", optional: true},
58 | {:ex_doc, "~> 0.18", only: [:dev, :release], optional: true, runtime: false},
59 | {:dialyxir, "~> 1.0", only: [:dev, :test], optional: true, runtime: false},
60 | {:phoenix_html, "~> 3.0", only: [:dev, :test, :release], optional: true, runtime: false},
61 | {:exprof, "~> 0.2", only: :dev, optional: true, runtime: false},
62 | # {:benchee, "~> 1.0", only: :dev, optional: true, runtime: false}
63 | ]
64 | end
65 |
66 | defp package do
67 | [
68 | maintainers: ["Kip Cole"],
69 | licenses: ["Apache-2.0"],
70 | links: links(),
71 | files: [
72 | "lib",
73 | "src/decimal_formats_lexer.xrl",
74 | "src/decimal_formats_parser.yrl",
75 | "src/rbnf_lexer.xrl",
76 | "src/rbnf_parser.yrl",
77 | "config",
78 | "mix.exs",
79 | "README*",
80 | "CHANGELOG*",
81 | "LICENSE*"
82 | ]
83 | ]
84 | end
85 |
86 | def docs do
87 | [
88 | source_ref: "v#{@version}",
89 | main: "readme",
90 | extras: ["README.md", "CHANGELOG.md", "LICENSE.md"],
91 | logo: "logo.png",
92 | formatters: ["html"],
93 | skip_undefined_reference_warnings_on: ["changelog", "CHANGELOG.md"]
94 | ]
95 | end
96 |
97 | def links do
98 | %{
99 | "GitHub" => "https://github.com/elixir-cldr/cldr_numbers",
100 | "Readme" => "https://github.com/elixir-cldr/cldr_numbers/blob/v#{@version}/README.md",
101 | "Changelog" => "https://github.com/elixir-cldr/cldr_numbers/blob/v#{@version}/CHANGELOG.md"
102 | }
103 | end
104 |
105 | defp elixirc_paths(:test), do: ["lib", "test", "mix"]
106 | defp elixirc_paths(:dev), do: ["lib", "mix"]
107 | defp elixirc_paths(_), do: ["lib"]
108 | end
109 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"},
3 | "cldr_utils": {:hex, :cldr_utils, "2.28.2", "f500667164a9043369071e4f9dcef31f88b8589b2e2c07a1eb9f9fa53cb1dce9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "c506eb1a170ba7cdca59b304ba02a56795ed119856662f6b1a420af80ec42551"},
4 | "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
5 | "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
6 | "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
7 | "digital_token": {:hex, :digital_token, "1.0.0", "454a4444061943f7349a51ef74b7fb1ebd19e6a94f43ef711f7dae88c09347df", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "8ed6f5a8c2fa7b07147b9963db506a1b4c7475d9afca6492136535b064c9e9e6"},
8 | "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
9 | "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
10 | "ex_cldr": {:hex, :ex_cldr, "2.42.0", "17ea930e88b8802b330e1c1e288cdbaba52cbfafcccf371ed34b299a47101ffb", [:mix], [{:cldr_utils, "~> 2.28", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "07264a7225810ecae6bdd6715d8800c037a1248dc0063923cddc4ca3c4888df6"},
11 | "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.16.4", "d76770690699b6ba91f1fa253a299a905f9c22b45d91891b85f431b9dafa8b3b", [:mix], [{:ex_cldr, "~> 2.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "46a67d1387f14e836b1a24d831fa5f0904663b4f386420736f40a7d534e3cb9e"},
12 | "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"},
13 | "exprintf": {:hex, :exprintf, "0.2.1", "b7e895dfb00520cfb7fc1671303b63b37dc3897c59be7cbf1ae62f766a8a0314", [:mix], [], "hexpm", "20a0e8c880be90e56a77fcc82533c5d60c643915c7ce0cc8aa1e06ed6001da28"},
14 | "exprof": {:hex, :exprof, "0.2.4", "13ddc0575a6d24b52e7c6809d2a46e9ad63a4dd179628698cdbb6c1f6e497c98", [:mix], [{:exprintf, "~> 0.2", [hex: :exprintf, repo: "hexpm", optional: false]}], "hexpm", "0884bcb66afc421c75d749156acbb99034cc7db6d3b116c32e36f32551106957"},
15 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
16 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
17 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
18 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
19 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
20 | "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
21 | "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
22 | }
23 |
--------------------------------------------------------------------------------
/mix/clause_debugger.ex_:
--------------------------------------------------------------------------------
1 | defmodule Cldr.FunctionClause do
2 | @moduledoc """
3 | Format function clauses using Exception.blame/3
4 | """
5 |
6 | @doc """
7 | Given a `module`, `function`, and `args` see
8 | that function clause would match or not match.
9 |
10 | This is useful for helping diagnose function
11 | clause errors when many clauses are generated
12 | at compile time.
13 |
14 | """
15 | @spec match(module(), atom(), list(any)) :: :ok | no_return()
16 | def match(module, function, args) do
17 | case Exception.blame_mfa(module, function, args) do
18 | {:ok, kind, clauses} ->
19 | formatted_clauses(function, kind, clauses, &blame_match/2)
20 |
21 | :error ->
22 | raise ArgumentError,
23 | "Function #{inspect(module)}.#{function}/#{length(args)} " <>
24 | "is not known."
25 | end
26 | end
27 |
28 | defp formatted_clauses(function, kind, clauses, ast_fun) do
29 | format_clause_fun = fn {args, guards} ->
30 | code = Enum.reduce(guards, {function, [], args}, &{:when, [], [&2, &1]})
31 | " #{kind} " <> Macro.to_string(code, ast_fun) <> "\n"
32 | end
33 |
34 | clauses
35 | |> Enum.map(format_clause_fun)
36 | |> Enum.join()
37 | |> IO.puts()
38 | end
39 |
40 | defp blame_match(%{match?: true, node: node}, _),
41 | do: Macro.to_string(node)
42 |
43 | defp blame_match(%{match?: false, node: node}, _),
44 | do: IO.ANSI.red() <> Macro.to_string(node) <> IO.ANSI.reset()
45 |
46 | defp blame_match(_, string), do: string
47 | end
48 |
--------------------------------------------------------------------------------
/mix/for_dialyzer.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.ForDialyzer do
2 | @moduledoc """
3 | Includes functions intended only to give dialyzer some
4 | opportunity to check specs.
5 |
6 | """
7 |
8 | def for_dialyzer do
9 | formats = MyApp.Cldr.Number.Format.all_formats_for!(:en)
10 | formats
11 | end
12 | end
--------------------------------------------------------------------------------
/mix/rbnf_test_data_gen.ex:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Rbnf.Gen do
2 | @moduledoc """
3 | Generates test data values for RBNF.
4 |
5 | Only to be used if something changes in
6 | CLDR
7 |
8 | The json included here has the standard test
9 | keys used by RBNF testing. The values will be
10 | replaced with generated data.
11 |
12 | """
13 | @json ~S"""
14 | {
15 | "-1141": "−1 141.",
16 | "-1142": "−1 142.",
17 | "-1143": "−1 143.",
18 | "-100": "−100.",
19 | "-75": "−75-и",
20 | "-50": "−50.",
21 | "-24": "−24.",
22 | "0": "0.",
23 | "1": "1-ви",
24 | "2": "2-и",
25 | "3": "3-и",
26 | "4": "4-ти",
27 | "5": "5-и",
28 | "6": "6-и",
29 | "7": "7-и",
30 | "8": "8-и",
31 | "9": "9-и",
32 | "10": "10-и",
33 | "11": "11-и",
34 | "12": "12-и",
35 | "13": "13-и",
36 | "14": "14-и",
37 | "15": "15-и",
38 | "16": "16-и",
39 | "17": "17-и",
40 | "18": "18-и",
41 | "19": "19-и",
42 | "20": "20-и",
43 | "21": "21-и",
44 | "22": "22-и",
45 | "23": "23-и",
46 | "24": "24-ти",
47 | "25": "25-и",
48 | "26": "26-и",
49 | "27": "27-и",
50 | "28": "28-и",
51 | "29": "29-и",
52 | "30": "30-и",
53 | "31": "31-и",
54 | "32": "32-и",
55 | "33": "33-и",
56 | "34": "34-и",
57 | "35": "35-и",
58 | "36": "36-и",
59 | "37": "37-и",
60 | "38": "38-и",
61 | "39": "39-и",
62 | "40": "40-и",
63 | "41": "41-и",
64 | "42": "42-ри",
65 | "43": "43-и",
66 | "44": "44-и",
67 | "45": "45-и",
68 | "46": "46-и",
69 | "47": "47-и",
70 | "48": "48-и",
71 | "49": "49-и",
72 | "50": "50-и",
73 | "51": "51-ви",
74 | "52": "52-и",
75 | "53": "53-и",
76 | "54": "54-и",
77 | "55": "55-и",
78 | "56": "56-и",
79 | "57": "57-и",
80 | "58": "58-и",
81 | "59": "59-и",
82 | "60": "60-и",
83 | "61": "61-и",
84 | "62": "62-и",
85 | "63": "63-и",
86 | "64": "64-и",
87 | "65": "65-и",
88 | "66": "66-и",
89 | "67": "67-и",
90 | "68": "68-и",
91 | "69": "69-и",
92 | "70": "70-и",
93 | "71": "71-и",
94 | "72": "72-и",
95 | "73": "73-и",
96 | "74": "74-и",
97 | "75": "75-и",
98 | "76": "76-и",
99 | "77": "77-и",
100 | "78": "78-и",
101 | "79": "79-и",
102 | "80": "80-и",
103 | "81": "81-и",
104 | "82": "82-и",
105 | "83": "83-и",
106 | "84": "84-и",
107 | "85": "85-и",
108 | "86": "86-и",
109 | "87": "87-и",
110 | "88": "88-и",
111 | "89": "89-и",
112 | "90": "90-и",
113 | "91": "91-и",
114 | "92": "92-и",
115 | "93": "93-и",
116 | "94": "94-и",
117 | "95": "95-и",
118 | "96": "96-и",
119 | "97": "97-и",
120 | "98": "98-и",
121 | "99": "99-и",
122 | "100": "100.",
123 | "321": "321.",
124 | "322": "322.",
125 | "323": "323.",
126 | "1141": "1141-и",
127 | "1142": "1142-и",
128 | "1143": "1143-и",
129 | "10311": "10 311.",
130 | "138400": "138 400-тен"
131 | }
132 | """
133 |
134 | def gen(rule, locale) do
135 | @json
136 | |> Jason.decode!()
137 | |> Enum.map(fn {k, _v} ->
138 | i = String.to_integer(k)
139 | {:ok, v} = Cldr.Number.to_string(i, format: rule, locale: locale)
140 | {i, v}
141 | end)
142 | |> Enum.sort()
143 | |> Enum.map(fn {k, v} -> "\"#{k}\": \"#{v}\"" end)
144 | |> Enum.join(",\n")
145 | |> IO.puts()
146 |
147 | nil
148 | end
149 | end
150 |
--------------------------------------------------------------------------------
/mix/tasks/generate_rbnf_test_data.ex:
--------------------------------------------------------------------------------
1 | defmodule Mix.Tasks.Cldr.Number.GenerateRbnfTestData do
2 | @moduledoc """
3 | Generates RBNF test data
4 | """
5 |
6 | use Mix.Task
7 | require Logger
8 |
9 | @shortdoc "Generate RBNF test data"
10 |
11 | @output_directory "test/support/rbnf"
12 |
13 | # Check out possible bug being surfaced in PL
14 | @locales [
15 | :pl
16 | # :af, :be, :bg, :ca, :es, :gu, :he, :hi, :hr, :hu, :it, :ja, :ko, :ms, :ru,
17 | # :uk,:vi, :zh, :"zh-Hant"
18 | ]
19 |
20 | @doc false
21 | def run(_) do
22 | for locale <- @locales do
23 | IO.puts "Generating RBNF test data for locale #{locale}"
24 |
25 | tests =
26 | "#{@output_directory}/#{locale}/rbnf_test.json"
27 | |> File.read!()
28 | |> Jason.decode!()
29 | |> Enum.map(fn {rule_group, rule_examples} -> {rule_group, generate_tests(locale, rule_examples)} end)
30 | |> Map.new()
31 | |> Jason.encode!(pretty: true)
32 |
33 | File.write("#{@output_directory}/#{locale}/rbnf_test.json", tests)
34 | end
35 | end
36 |
37 | defp generate_tests(locale, rule_examples) do
38 | Enum.map(rule_examples, fn {rule_string, examples} ->
39 | rule =
40 | rule_string
41 | |> String.replace("-", "_")
42 | |> String.to_atom()
43 |
44 | IO.puts " Building examples for rule #{rule}"
45 |
46 | examples =
47 | Enum.map(examples, fn {number, _result} ->
48 | int = String.to_integer(number)
49 | {:ok, result} = Cldr.Number.to_string int, format: rule, locale: locale, backend: TestBackend.Cldr
50 | {number, result}
51 | end)
52 | |> Map.new()
53 |
54 | {rule_string, examples}
55 | end)
56 | |> Map.new()
57 | end
58 | end
--------------------------------------------------------------------------------
/mix/test_backend.ex:
--------------------------------------------------------------------------------
1 | require Cldr.Number.Backend
2 |
3 | defmodule MyApp.Cldr do
4 | @moduledoc false
5 |
6 | use Cldr,
7 | default_locale: "en",
8 | locales: [
9 | "en",
10 | "da",
11 | "bn",
12 | "zh",
13 | "zh-Hant",
14 | "it",
15 | "de",
16 | "th",
17 | "id",
18 | "ru",
19 | "he",
20 | "pl",
21 | "es",
22 | "hr",
23 | "nb",
24 | "no",
25 | "en-IN",
26 | "ur",
27 | "fr-CH",
28 | "fr-BE",
29 | "ta",
30 | "he",
31 | "pt-CV",
32 | "ar-EG",
33 | "en-ZA"
34 | ],
35 | precompile_transliterations: [{:latn, :arab}, {:arab, :thai}, {:arab, :latn}],
36 | providers: [Cldr.Number],
37 | suppress_warnings: true
38 | end
39 |
40 | defmodule MyApp.Cldr2 do
41 | @moduledoc false
42 |
43 | use Cldr,
44 | default_locale: "en-GB",
45 | locales: ["en-GB", "hu", "ar", "de"],
46 | precompile_transliterations: [{:latn, :arab}, {:arab, :thai}, {:arab, :latn}, {:thai, :latn}],
47 | providers: [Cldr.Number],
48 | suppress_warnings: true
49 | end
50 |
--------------------------------------------------------------------------------
/src/decimal_formats_lexer.xrl:
--------------------------------------------------------------------------------
1 | % Tokenizes CLDR decimal formats which are described at
2 | % http://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns
3 |
4 | Definitions.
5 |
6 | Number = ([@#,]*)?([0-9,]+)?(\.[0-9#,]+)?([Ee](\+)?[0-9]+)?
7 | Percent = %
8 | Permille = ‰
9 | Plus = \+
10 | Minus = \-
11 | Semicolon = ;
12 | Currency = ¤+
13 | Pad = \*.
14 | Quoted = \'.\'
15 | Quote = \'\'
16 | Literal = [^*@#0-9¤\+\-;%\']+
17 |
18 | Rules.
19 |
20 | {Number} : {token,{format,TokenLine,TokenChars}}.
21 | {Percent} : {token,{percent,TokenLine,TokenChars}}.
22 | {Permille} : {token,{permille,TokenLine,TokenChars}}.
23 | {Plus} : {token,{plus,TokenLine,TokenChars}}.
24 | {Minus} : {token,{minus,TokenLine,TokenChars}}.
25 | {Semicolon} : {token,{semicolon,TokenLine,TokenChars}}.
26 | {Currency} : {token,{currency_symbol(length(TokenChars)),TokenLine,length(TokenChars)}}.
27 | {Pad} : {token,{pad,TokenLine,[lists:nth(2,TokenChars)]}}.
28 | {Quoted} : {token,{quoted_char,TokenLine,[lists:nth(2, TokenChars)]}}.
29 | {Quote} : {token,{quote,TokenLine,["'"]}}.
30 | {Literal} : {token,{literal,TokenLine,TokenChars}}.
31 |
32 | Erlang code.
33 |
34 | currency_symbol(Len) ->
35 | list_to_atom("currency_" ++ integer_to_list(Len)).
--------------------------------------------------------------------------------
/src/decimal_formats_parser.yrl:
--------------------------------------------------------------------------------
1 | % Parse CLDR decimal formats which are described at
2 | % http://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns
3 |
4 | Nonterminals decimal_format positive_format negative_format number_format
5 | prefix suffix literal_list literal_elem currency_symbol.
6 |
7 | Terminals plus minus format currency_1 currency_2 currency_3 currency_4 percent
8 | permille literal semicolon pad quote quoted_char.
9 |
10 | Rootsymbol decimal_format.
11 |
12 | decimal_format -> positive_format semicolon negative_format : [{positive, '$1'}, {negative, '$3'}].
13 | decimal_format -> positive_format : [{positive, '$1'}, negative('$1')].
14 |
15 | positive_format -> prefix number_format suffix : '$1' ++ format('$2') ++ '$3'.
16 | positive_format -> prefix number_format : '$1' ++ format('$2').
17 | positive_format -> number_format suffix : format('$1') ++ '$2'.
18 | positive_format -> number_format : format('$1').
19 | positive_format -> prefix : '$1'.
20 |
21 | negative_format -> prefix number_format suffix : '$1' ++ neg_format('$2') ++ '$3'.
22 | negative_format -> prefix number_format : '$1' ++ neg_format('$2').
23 | negative_format -> number_format suffix : neg_format('$1') ++ '$2'.
24 | negative_format -> number_format : neg_format('$1').
25 |
26 | number_format -> format : unwrap('$1').
27 |
28 | prefix -> literal_list pad : '$1' ++ pad('$2').
29 | prefix -> pad literal_list : pad('$1') ++ '$2'.
30 | prefix -> literal_list : '$1'.
31 | prefix -> pad : pad('$1').
32 |
33 | suffix -> prefix : '$1'.
34 |
35 | literal_list -> literal_elem literal_list : append('$1', '$2').
36 | literal_list -> literal_elem : '$1'.
37 |
38 | literal_elem -> currency_symbol : [{currency, unwrap('$1')}].
39 | literal_elem -> percent : [{percent, unwrap('$1')}].
40 | literal_elem -> permille : [{permille, unwrap('$1')}].
41 | literal_elem -> literal : [{literal, unwrap('$1')}].
42 | literal_elem -> plus : [{plus, "+"}].
43 | literal_elem -> minus : [{minus, "-"}].
44 | literal_elem -> quote : [{quote, '\''}].
45 | literal_elem -> quoted_char : [{quoted_char, unwrap('$1')}].
46 |
47 | currency_symbol -> currency_1 : '$1'.
48 | currency_symbol -> currency_2 : '$1'.
49 | currency_symbol -> currency_3 : '$1'.
50 | currency_symbol -> currency_4 : '$1'.
51 |
52 | Erlang code.
53 |
54 | % If there is no negative pattern then build the default one. The default
55 | % is an exact copy of the positive one except we put `{minus, "-"}`
56 | % in front of the `format` token.
57 | negative(Positive) ->
58 | {negative, copy_positive(Positive)}.
59 |
60 | copy_positive([]) -> [];
61 | copy_positive([{format, Format} | Rest]) -> [{minus, "-"}, {format, Format} | copy_positive(Rest)];
62 | copy_positive([Head | Rest]) -> [Head | copy_positive(Rest)].
63 |
64 | % Append list items. Consolidate literals if possible into
65 | % a single list element.
66 | append([{literal, Literal1}], [{literal, Literal2} | Rest]) ->
67 | [{literal, list_to_binary([Literal1, Literal2])}] ++ Rest;
68 | append(A, B) when is_list(A) and is_list(B) ->
69 | A ++ B.
70 |
71 | format(F) ->
72 | [{format, F}].
73 |
74 | % Doesn't matter what the negative format is
75 | % its always the same as the positive one
76 | % with potentially different suffix and prefix
77 | neg_format(_F) ->
78 | [{format, same_as_positive}].
79 |
80 | pad(V) ->
81 | [{pad, unwrap(V)}].
82 |
83 | % Return a token value
84 | unwrap({_,_,V}) when is_list(V) -> unicode:characters_to_binary(V);
85 | unwrap({_,_,V}) -> V.
86 |
--------------------------------------------------------------------------------
/src/rbnf_lexer.xrl:
--------------------------------------------------------------------------------
1 | % Lexer for the ICU/CLDR Rule Based Number Formatting rule definitions.
2 | % Step 1 is to read in the json rule set.
3 | % Then for each ruleset, pass each rule to the parser in two parts:
4 | % First the rule name
5 | % THen the rule definition
6 | %
7 | % Can be invoked in elixir as:
8 | %
9 | % iex> :rbnf.string(rules_set_as_a_char_list)
10 | %
11 |
12 | Definitions.
13 |
14 | Rule_cardinal_start = (cardinal)
15 | Rule_ordinal_start = (ordinal)
16 | Plural_rules = (zero\{.+\})?(one\{.+\})?(two\{.+\})?(few\{.+\})?(many\{.+\})?(other\{.+\})?
17 |
18 | Rule_name = (%[%a-zA-Z0-9\-]+)
19 | Number_format = ([0#]([0#,]+)?)(\.([0#]+))?([eE]([-+]?[0#]+))?
20 | Conditional_start = \[
21 | Conditional_end = \]
22 | Left_paren = \(
23 | Right_paren = \)
24 | Greater_than = [→>]
25 | Less_than = [←<]
26 | Equals = =
27 | Dollar = \$
28 | Semicolon = ;
29 | Comma = ,
30 | Char = .
31 |
32 | Rules.
33 |
34 | % This part is for the definitions.
35 | {Rule_cardinal_start} : {token,{rule_cardinal_start,TokenLine,TokenChars}}.
36 | {Rule_ordinal_start} : {token,{rule_ordinal_start,TokenLine,TokenChars}}.
37 | {Number_format} : {token,{number_format,TokenLine,TokenChars}}.
38 | {Rule_name} : {token,{rule_name,TokenLine,TokenChars}}.
39 | {Conditional_start} : {token,{conditional_start,TokenLine,TokenChars}}.
40 | {Conditional_end} : {token,{conditional_end,TokenLine,TokenChars}}.
41 | {Greater_than} : {token,{modulo_call,TokenLine,TokenChars}}.
42 | {Less_than} : {token,{quotient_call,TokenLine,TokenChars}}.
43 | {Equals} : {token,{rule_call,TokenLine,TokenChars}}.
44 | {Semicolon} : {token,{rule_end,TokenLine,TokenChars}}.
45 | {Plural_rules} : {token,{plural_rules,TokenLine,TokenChars}}.
46 | {Right_paren} : {token,{right_paren,TokenLine,TokenChars}}.
47 | {Left_paren} : {token,{left_paren,TokenLine,TokenChars}}.
48 | {Dollar} : {token,{dollar,TokenLine,TokenChars}}.
49 | {Comma} : {token,{comma,TokenLine,TokenChars}}.
50 | {Char} : {token,{char,TokenLine,TokenChars}}.
51 |
52 | Erlang code.
53 |
--------------------------------------------------------------------------------
/src/rbnf_parser.yrl:
--------------------------------------------------------------------------------
1 | Nonterminals quotient_rule modulo_rule invoke_rule cardinal_rule ordinal_rule
2 | rule literal rbnf_rule rule_part character.
3 |
4 | Terminals rule_cardinal_start rule_ordinal_start number_format
5 | rule_name conditional_start conditional_end modulo_call
6 | quotient_call rule_call plural_rules right_paren
7 | left_paren dollar comma char.
8 |
9 | Rootsymbol rbnf_rule.
10 |
11 | Left 100 rbnf_rule.
12 | Right 400 modulo_call.
13 | Right 500 quotient_call.
14 | Right 600 character.
15 |
16 | rbnf_rule -> rule_part rbnf_rule : ['$1'] ++ '$2'.
17 | rbnf_rule -> rule_part : ['$1'].
18 |
19 | rule_part -> literal : '$1'.
20 | rule_part -> quotient_rule : '$1'.
21 | rule_part -> modulo_rule : '$1'.
22 | rule_part -> invoke_rule : '$1'.
23 | rule_part -> cardinal_rule : '$1'.
24 | rule_part -> ordinal_rule : '$1'.
25 | rule_part -> conditional_start rbnf_rule conditional_end : {conditional, '$2'}.
26 |
27 | quotient_rule -> quotient_call rule quotient_call quotient_call : {quotient, '$2'}.
28 | quotient_rule -> quotient_call rule quotient_call : {quotient, '$2'}.
29 | quotient_rule -> quotient_call quotient_call : {quotient, nil}.
30 |
31 | modulo_rule -> modulo_call rule modulo_call : {modulo, '$2'}.
32 | modulo_rule -> modulo_call modulo_call : {modulo, nil}.
33 |
34 | % Note that here we are treating >>> as equivalent to >>
35 | % This is not strictly true since the spec says we should
36 | % ... but bypass the normal rule-selection process and just
37 | % use the rule that precedes this one in this rule list.
38 | modulo_rule -> modulo_call modulo_call modulo_call : {modulo, nil}.
39 |
40 |
41 | invoke_rule -> rule_call rule rule_call : {call, '$2'}.
42 |
43 | cardinal_rule -> dollar left_paren rule_cardinal_start comma
44 | plural_rules right_paren dollar : {cardinal, to_map('$5')}.
45 | ordinal_rule -> dollar left_paren rule_ordinal_start comma
46 | plural_rules right_paren dollar : {ordinal, to_map('$5')}.
47 |
48 | rule -> rule_name : {rule, normalize_rule_name('$1')}.
49 | rule -> number_format : {format, unwrap('$1')}.
50 |
51 | literal -> character literal : append('$1', '$2').
52 | literal -> character : {literal, unwrap('$1')}.
53 |
54 | character -> char : '$1'.
55 | character -> comma : '$1'.
56 | character -> number_format : '$1'.
57 |
58 | Erlang code.
59 |
60 | % Consolidate characters into a binary
61 | append({char, _, _} = Char, {literal, Literal}) ->
62 | {literal, list_to_binary([unwrap(Char), Literal])};
63 | append({comma, _, _} = Char, {literal, Literal}) ->
64 | {literal, list_to_binary([unwrap(Char), Literal])};
65 | append({number_format, _, _} = Char, {literal, Literal}) ->
66 | {literal, list_to_binary([unwrap(Char), Literal])};
67 | append({literal, Literal1}, {literal, Literal2}) ->
68 | {literal, list_to_binary([Literal2, Literal1])}.
69 |
70 | % We will turn rule names into functions later on so
71 | % we normalise the names to a format that is acceptable.
72 | normalize_rule_name({_,_,[$%, $% | Name]}) ->
73 | erlang:binary_to_atom(unicode:characters_to_binary(force_valid_function_name(Name)), utf8);
74 | normalize_rule_name({_,_,[$% | Name]}) ->
75 | erlang:binary_to_atom(unicode:characters_to_binary(force_valid_function_name(Name)), utf8).
76 |
77 | % Return a token value as a binary
78 | unwrap({_,_,V}) when is_list(V) -> unicode:characters_to_binary(V);
79 | unwrap({_,_,V}) -> V.
80 |
81 | % Force a valid function name. This is in two parts:
82 | % 1. If the function name starts with a number, prepend
83 | % a letter
84 | % 2. Replace "-" with "_"
85 | force_valid_function_name([Digit | Rest]) when Digit >= $0 andalso Digit =< $9 ->
86 | underscore([$r, Digit | Rest]);
87 | force_valid_function_name(Other) ->
88 | underscore(Other).
89 |
90 | % Substitute "_" for "-" since we will use these rule names
91 | % as functions later on.
92 | underscore([$-| Rest]) ->
93 | [$_ | underscore(Rest)];
94 | underscore([]) ->
95 | [];
96 | underscore([Char | Rest]) ->
97 | [Char | underscore(Rest)].
98 |
99 | % Convert ordinal and cardinal rules into a map
100 | to_map(Plurals) ->
101 | String = unwrap(Plurals),
102 | Parts = binary:split(String, [<<"{">>,<<"}">>], [global, trim]),
103 | Proplist = to_proplist(Parts),
104 | maps:from_list(Proplist).
105 |
106 | % Convert a list into a proplist
107 | to_proplist([K,V | T]) -> [{erlang:binary_to_atom(K, utf8),V} | to_proplist(T)];
108 | to_proplist([]) -> [].
--------------------------------------------------------------------------------
/test/cldr_numbers_test.exs:
--------------------------------------------------------------------------------
1 | defmodule CldrNumbersTest do
2 | use ExUnit.Case, async: true
3 |
4 | if function_exported?(Code, :fetch_docs, 1) do
5 | @modules [
6 | Number,
7 | Number.System,
8 | Number.Format,
9 | Number.Symbol,
10 | Number.Transliterate,
11 | Number.Formatter.Decimal,
12 | Rbnf.NumberSystem,
13 | Rbnf.Spellout,
14 | Rbnf.Ordinal
15 | ]
16 |
17 | test "that no module docs are generated for a backend" do
18 | for mod <- @modules do
19 | module = Module.concat(NoDoc.Cldr, mod)
20 | assert {:docs_v1, _, :elixir, _, :hidden, %{}, _} = Code.fetch_docs(module)
21 | end
22 | end
23 |
24 | assert "that module docs are generated for a backend" do
25 | for mod <- @modules do
26 | module = Module.concat(TestBackend.Cldr, mod)
27 | {:docs_v1, _, :elixir, "text/markdown", _, %{}, _} = Code.fetch_docs(module)
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/test/doc_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Doc.Test do
2 | use ExUnit.Case
3 |
4 | default = Application.compile_env(:ex_cldr, :default_backend)
5 | Application.put_env(:ex_cldr, :default_backend, TestBackend.Cldr)
6 |
7 | doctest Cldr.Number
8 | doctest Cldr.Number.String
9 | doctest Cldr.Number.Format
10 | doctest Cldr.Number.Symbol
11 | doctest Cldr.Number.System
12 | doctest Cldr.Number.Transliterate
13 | doctest Cldr.Number.Format.Compiler
14 | doctest Cldr.Number.Parser
15 |
16 | doctest Cldr.Number.Formatter.Decimal
17 | doctest Cldr.Number.Formatter.Short
18 | doctest Cldr.Number.Formatter.Currency
19 |
20 | doctest TestBackend.Cldr.Number.System
21 | doctest TestBackend.Cldr.Number
22 | doctest TestBackend.Cldr.Number.Symbol
23 | doctest TestBackend.Cldr.Number.Format
24 | doctest TestBackend.Cldr.Number.Transliterate
25 |
26 | doctest TestBackend.Cldr.Rbnf.Ordinal
27 | doctest TestBackend.Cldr.Rbnf.Spellout
28 | doctest TestBackend.Cldr.Rbnf.NumberSystem
29 |
30 | Application.put_env(:ex_cldr, :default_backend, default)
31 | end
32 |
--------------------------------------------------------------------------------
/test/meta_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Format.Meta.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "that we can create a default metadata struct" do
5 | assert Cldr.Number.Format.Meta.new() ==
6 | %Cldr.Number.Format.Meta{
7 | exponent_digits: 0,
8 | exponent_sign: false,
9 | format: [
10 | positive: [format: "#"],
11 | negative: [minus: ~c"-", format: :same_as_positive]
12 | ],
13 | fractional_digits: %{max: 0, min: 0},
14 | grouping: %{fraction: %{first: 0, rest: 0}, integer: %{first: 0, rest: 0}},
15 | integer_digits: %{max: 0, min: 1},
16 | multiplier: 1,
17 | number: 0,
18 | padding_char: " ",
19 | padding_length: 0,
20 | round_nearest: 0,
21 | scientific_rounding: 0,
22 | significant_digits: %{max: 0, min: 0}
23 | }
24 | end
25 |
26 | test "setting the meta fields" do
27 | alias Cldr.Number.Format.Meta
28 |
29 | meta =
30 | Meta.new()
31 | |> Meta.put_integer_digits(2)
32 | |> Meta.put_fraction_digits(3)
33 | |> Meta.put_significant_digits(4)
34 |
35 | assert meta.integer_digits == %{max: 0, min: 2}
36 | assert meta.fractional_digits == %{max: 0, min: 3}
37 | assert meta.significant_digits == %{max: 0, min: 4}
38 |
39 | meta =
40 | meta
41 | |> Meta.put_exponent_digits(5)
42 | |> Meta.put_exponent_sign(true)
43 | |> Meta.put_scientific_rounding_digits(6)
44 | |> Meta.put_round_nearest_digits(7)
45 | |> Meta.put_padding_length(8)
46 | |> Meta.put_padding_char("Z")
47 | |> Meta.put_multiplier(9)
48 |
49 | assert meta.exponent_digits == 5
50 | assert meta.exponent_sign == true
51 | assert meta.scientific_rounding == 6
52 | assert meta.round_nearest == 7
53 | assert meta.padding_length == 8
54 | assert meta.padding_char == "Z"
55 | assert meta.multiplier == 9
56 |
57 | meta =
58 | meta
59 | |> Meta.put_fraction_grouping(10)
60 | |> Meta.put_integer_grouping(11)
61 |
62 | assert meta.grouping == %{
63 | fraction: %{first: 10, rest: 10},
64 | integer: %{first: 11, rest: 11}
65 | }
66 |
67 | meta =
68 | Meta.new()
69 | |> Meta.put_integer_digits(12, 13)
70 | |> Meta.put_fraction_digits(14, 15)
71 | |> Meta.put_significant_digits(16, 17)
72 |
73 | assert meta.integer_digits == %{max: 13, min: 12}
74 | assert meta.fractional_digits == %{max: 15, min: 14}
75 | assert meta.significant_digits == %{max: 17, min: 16}
76 |
77 | meta =
78 | Meta.new()
79 | |> Meta.put_format([format: "#"], format: "##")
80 |
81 | assert meta.format == [positive: [format: "#"], negative: [format: "##"]]
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/test/number/currency_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.CurrencyTest do
2 | use ExUnit.Case, async: true
3 |
4 | test "That the currency is derived from the locale" do
5 | assert {:ok, "AU$๑๒๓.๐๐"} ==
6 | MyApp.Cldr.Number.to_string(123,
7 | locale: "th-u-cu-aud-nu-thai",
8 | currency: :from_locale
9 | )
10 |
11 | assert {:ok, "฿๑๒๓.๐๐"} ==
12 | MyApp.Cldr.Number.to_string(123, locale: "th-u-nu-thai", currency: :from_locale)
13 |
14 | assert {:ok, "฿123.00"} ==
15 | MyApp.Cldr.Number.to_string(123, locale: "th", currency: :from_locale)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/number/decimal_format_compiler_test.exs:
--------------------------------------------------------------------------------
1 | defmodule DecimalFormatCompiler.Test do
2 | use ExUnit.Case, async: true
3 |
4 | alias Cldr.Number.Format
5 |
6 | Enum.each(TestBackend.Cldr.Number.Format.decimal_format_list(), fn format ->
7 | test "Compile decimal format #{inspect(format)}" do
8 | assert {:ok, _result} = Format.Compiler.parse(unquote(format))
9 | end
10 | end)
11 |
12 | test "compile fails on empty format" do
13 | assert {:error, _result} = Format.Compiler.parse("")
14 | end
15 |
16 | test "compile fails on nil format" do
17 | assert {:error, _result} = Format.Compiler.parse(nil)
18 | end
19 |
20 | test "that we can parse a token list" do
21 | {:ok, tokens, _} = Format.Compiler.tokenize("#")
22 | assert {:ok, _parse_tree} = Format.Compiler.parse(tokens)
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/test/number/exponent_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Exponent.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "An integer number exponent format with precision" do
5 | assert {:ok, "1.2E3"} = MyApp.Cldr.Number.to_string(1234, format: "0.0E0")
6 | assert {:ok, "1.23E3"} = MyApp.Cldr.Number.to_string(1234, format: "0.00E0")
7 | assert {:ok, "1.234E3"} = MyApp.Cldr.Number.to_string(1234, format: "0.000E0")
8 | end
9 |
10 | test "A float number exponent format with precision" do
11 | assert {:ok, "1.2E3"} = MyApp.Cldr.Number.to_string(1234.5678, format: "0.0E0")
12 | assert {:ok, "1.23E3"} = MyApp.Cldr.Number.to_string(1234.5678, format: "0.00E0")
13 | assert {:ok, "1.235E3"} = MyApp.Cldr.Number.to_string(1234.5678, format: "0.000E0")
14 | assert {:ok, "1.23457E3"} = MyApp.Cldr.Number.to_string(1234.5678, format: "0.00000E0")
15 | end
16 |
17 | test "A Decimal number exponent format with precision" do
18 | assert {:ok, "1.2E3"} = MyApp.Cldr.Number.to_string(Decimal.new("1234.5678"), format: "0.0E0")
19 |
20 | assert {:ok, "1.23E3"} =
21 | MyApp.Cldr.Number.to_string(Decimal.new("1234.5678"), format: "0.00E0")
22 |
23 | assert {:ok, "1.235E3"} =
24 | MyApp.Cldr.Number.to_string(Decimal.new("1234.5678"), format: "0.000E0")
25 |
26 | assert {:ok, "1.23457E3"} =
27 | MyApp.Cldr.Number.to_string(Decimal.new("1234.5678"), format: "0.00000E0")
28 | end
29 |
30 | test "An integer number exponent format with exponent precision" do
31 | assert {:ok, "1.2E03"} = MyApp.Cldr.Number.to_string(1234, format: "0.0E00")
32 | assert {:ok, "1.23E003"} = MyApp.Cldr.Number.to_string(1234, format: "0.00E000")
33 | assert {:ok, "1.234E0003"} = MyApp.Cldr.Number.to_string(1234, format: "0.000E0000")
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/number/number_format_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Number.Format.Test do
2 | use ExUnit.Case, async: true
3 |
4 | Enum.each(Cldr.Test.Number.Format.test_data(), fn {value, result, args} ->
5 | new_args =
6 | if args[:locale] do
7 | Keyword.put(args, :locale, TestBackend.Cldr.Locale.new!(Keyword.get(args, :locale)))
8 | else
9 | args
10 | end
11 |
12 | test "formatted #{inspect(value)} == #{inspect(result)} with args: #{inspect(args)}" do
13 | assert {:ok, unquote(result)} =
14 | TestBackend.Cldr.Number.to_string(unquote(value), unquote(Macro.escape(new_args)))
15 | end
16 | end)
17 |
18 | test "to_string with no arguments" do
19 | assert {:ok, "1,234"} = Cldr.Number.to_string(1234)
20 | end
21 |
22 | test "to_string with only options" do
23 | assert {:ok, "1.234"} = Cldr.Number.to_string(1234, locale: "de")
24 | end
25 |
26 | test "literal-only format returns the literal" do
27 | assert {:ok, "xxx"} = TestBackend.Cldr.Number.to_string(1234, format: "xxx")
28 | end
29 |
30 | test "formatted float with rounding" do
31 | assert {:ok, "1.40"} == TestBackend.Cldr.Number.to_string(1.4, fractional_digits: 2)
32 | end
33 |
34 | test "a currency format with no currency returns a currency formatted number" do
35 | assert {:ok, "1,234.00"} = TestBackend.Cldr.Number.to_string(1234, format: :currency)
36 | end
37 |
38 | test "that -0 is formatted as 0" do
39 | number = Decimal.new("-0")
40 | assert TestBackend.Cldr.Number.to_string(number) == {:ok, "0"}
41 | end
42 |
43 | test "minimum_grouping digits delegates to Cldr.Number.Symbol" do
44 | assert TestBackend.Cldr.Number.Format.minimum_grouping_digits_for!("en") == 1
45 | end
46 |
47 | test "that there are decimal formats for a locale" do
48 | assert Map.keys(TestBackend.Cldr.Number.Format.all_formats_for!("en")) == [:latn]
49 | end
50 |
51 | test "that there is an exception if we get formats for an unknown locale" do
52 | assert_raise Cldr.InvalidLanguageError, ~r/The language .* is invalid/, fn ->
53 | TestBackend.Cldr.Number.Format.formats_for!("zzz")
54 | end
55 | end
56 |
57 | test "that there is an exception if we get formats for an number system" do
58 | assert_raise Cldr.UnknownNumberSystemError, ~r/The number system \"zulu\" is invalid/, fn ->
59 | TestBackend.Cldr.Number.Format.formats_for!("en", "zulu")
60 | end
61 | end
62 |
63 | test "that an rbnf format request fails if the locale doesn't define the ruleset" do
64 | assert TestBackend.Cldr.Number.to_string(123, format: :spellout_ordinal_verbose, locale: "zh") ==
65 | {
66 | :error,
67 | {
68 | Cldr.Rbnf.NoRule,
69 | "RBNF rule :spellout_ordinal_verbose is unknown to locale TestBackend.Cldr.Locale.new!(\"zh-Hans-CN\")"
70 | }
71 | }
72 | end
73 |
74 | test "that we get default formats_for" do
75 | assert TestBackend.Cldr.Number.Format.formats_for!().__struct__ == Cldr.Number.Format
76 | end
77 |
78 | test "setting currency_format: :iso" do
79 | assert TestBackend.Cldr.Number.to_string(123, currency: :USD, currency_symbol: :iso) ==
80 | {:ok, "USD 123.00"}
81 | end
82 |
83 | test "round_nearest to_string parameter" do
84 | assert Cldr.Number.to_string(1234, MyApp.Cldr, round_nearest: 5) == {:ok, "1,235"}
85 | assert Cldr.Number.to_string(1231, MyApp.Cldr, round_nearest: 5) == {:ok, "1,230"}
86 | assert Cldr.Number.to_string(1234, MyApp.Cldr, round_nearest: 10) == {:ok, "1,230"}
87 | assert Cldr.Number.to_string(1231, MyApp.Cldr, round_nearest: 10) == {:ok, "1,230"}
88 | assert Cldr.Number.to_string(1235, MyApp.Cldr, round_nearest: 10) == {:ok, "1,240"}
89 | end
90 |
91 | test "fraction digits of 0" do
92 | assert Cldr.Number.to_string(50.12, MyApp.Cldr, fractional_digits: 0, currency: :USD) ==
93 | {:ok, "$50"}
94 |
95 | assert Cldr.Number.to_string(50.82, MyApp.Cldr, fractional_digits: 0, currency: :USD) ==
96 | {:ok, "$51"}
97 | end
98 |
99 | test "to_string with :percent format" do
100 | assert MyApp.Cldr.Number.to_string!(123.456, format: :percent, fractional_digits: 1) ==
101 | "12,345.6%"
102 | end
103 |
104 | test "negative decimal short formatting" do
105 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_000_000), format: :short) == {:ok, "1M"}
106 | assert MyApp.Cldr.Number.to_string(Decimal.new(-1_000_000), format: :short) == {:ok, "-1M"}
107 | assert MyApp.Cldr.Number.to_string(Decimal.new(100_000), format: :short) == {:ok, "100K"}
108 | assert MyApp.Cldr.Number.to_string(Decimal.new(-100_000), format: :short) == {:ok, "-100K"}
109 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_000), format: :short) == {:ok, "1K"}
110 | assert MyApp.Cldr.Number.to_string(Decimal.new(-1_000), format: :short) == {:ok, "-1K"}
111 | end
112 |
113 | test "Decimal currency short formatting" do
114 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_000_000), format: :currency_short) ==
115 | {:ok, "$1M"}
116 |
117 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_100_000),
118 | format: :currency_short,
119 | fractional_digits: 1
120 | ) == {:ok, "$1.1M"}
121 |
122 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_000_000), format: :short) == {:ok, "1M"}
123 |
124 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_100_000),
125 | format: :short,
126 | fractional_digits: 1
127 | ) == {:ok, "1.1M"}
128 | end
129 |
130 | test "Currency with :narrow formatting uses standard format with narrow symbol" do
131 | assert MyApp.Cldr.Number.to_string(Decimal.new(1_000_000), currency: :USD, format: :narrow) ==
132 | {:ok, "$1,000,000.00"}
133 | end
134 |
135 | test "Decimal currency short with fractional digits formatting" do
136 | assert Cldr.Number.to_string(Decimal.new("214564569.50"),
137 | format: :short,
138 | currency: :USD,
139 | fractional_digits: 2
140 | ) ==
141 | {:ok, "$214.56M"}
142 |
143 | assert Cldr.Number.to_string(Decimal.new("219.50"),
144 | format: :short,
145 | currency: :USD,
146 | fractional_digits: 2
147 | ) ==
148 | {:ok, "$219.50"}
149 |
150 | assert Cldr.Number.to_string(Decimal.from_float(219.50),
151 | format: :short,
152 | currency: :USD,
153 | fractional_digits: 2
154 | ) ==
155 | {:ok, "$219.50"}
156 | end
157 |
158 | test "Currency format long with symbol" do
159 | assert Cldr.Number.to_string(1_000_000_000, format: :currency_long_with_symbol, locale: "fr") ==
160 | {:ok, "1 milliard €"}
161 |
162 | assert Cldr.Number.to_string(1_000_000_000, format: :currency_long_with_symbol) ==
163 | {:ok, "$1 billion"}
164 |
165 | assert Cldr.Number.to_string(1_000_000_000, format: :currency_long_with_symbol, locale: "hr") ==
166 | {:ok, "1 milijardi €"}
167 |
168 | assert Cldr.Number.to_string(1_000_000_000,
169 | format: :currency_long_with_symbol,
170 | locale: "hr",
171 | currency: :USD
172 | ) ==
173 | {:ok, "1 milijardi USD"}
174 |
175 | assert Cldr.Number.to_string(1_234_545_656.456789,
176 | currency: "BTC",
177 | format: :currency_long_with_symbol
178 | ) ==
179 | {:ok, "₿1 billion"}
180 | end
181 |
182 | test "NaN and Inf decimal number formatting" do
183 | assert {:ok, "NaN"} = Cldr.Number.to_string(Decimal.new("NaN"))
184 | assert {:ok, "-NaN"} = Cldr.Number.to_string(Decimal.new("-NaN"))
185 | assert {:ok, "∞"} = Cldr.Number.to_string(Decimal.new("Inf"))
186 | assert {:ok, "-∞"} = Cldr.Number.to_string(Decimal.new("-Inf"))
187 | assert {:ok, "∞"} = Cldr.Number.to_string(Decimal.new("Inf"), locale: :de)
188 | assert {:ok, "-∞"} = Cldr.Number.to_string(Decimal.new("-Inf"), locale: :de)
189 |
190 | assert {:ok, "-∞"} =
191 | Cldr.Number.to_string(Decimal.new("-Inf"), format: "########", locale: :de)
192 |
193 | assert {:ok, "∞ and beyond"} =
194 | Cldr.Number.to_string(Decimal.new("Inf"), format: "# and beyond")
195 | end
196 |
197 | test "A free form currency format where the currency symbol is not first or last" do
198 | assert {:ok, "US$1,234"} =
199 | MyApp.Cldr.Number.to_string(1234, currency: "USD", format: "US¤#,###")
200 | end
201 |
202 | if System.otp_release() < "28" do
203 | test "Digital tokens with overriden symbols" do
204 | assert {:ok, "₿ 1,234,545,656.456789"} =
205 | Cldr.Number.to_string(1_234_545_656.456789,
206 | currency: "BTC",
207 | currency_symbol: :narrow
208 | )
209 |
210 | assert {:ok, "BTC 1,234,545,656.456789"} =
211 | Cldr.Number.to_string(1_234_545_656.456789, currency: "BTC", currency_symbol: :iso)
212 |
213 | assert {:ok, "₿ 1,234,545,656.456789"} =
214 | Cldr.Number.to_string(1_234_545_656.456789,
215 | currency: "BTC",
216 | currency_symbol: :symbol
217 | )
218 |
219 | assert {:ok, "₿ 1,234,545,656.456789"} =
220 | Cldr.Number.to_string(1_234_545_656.456789,
221 | currency: "BTC",
222 | currency_symbol: :standard
223 | )
224 |
225 | assert {:ok, "XBTC 1,234,545,656.456789"} =
226 | Cldr.Number.to_string(1_234_545_656.456789, currency: "BTC", currency_symbol: "XBTC")
227 | end
228 | else
229 | test "Digital tokens with overriden symbols" do
230 | assert {:ok, "₿1,234,545,656.456789"} =
231 | Cldr.Number.to_string(1_234_545_656.456789,
232 | currency: "BTC",
233 | currency_symbol: :narrow
234 | )
235 |
236 | assert {:ok, "BTC 1,234,545,656.456789"} =
237 | Cldr.Number.to_string(1_234_545_656.456789, currency: "BTC", currency_symbol: :iso)
238 |
239 | assert {:ok, "₿1,234,545,656.456789"} =
240 | Cldr.Number.to_string(1_234_545_656.456789,
241 | currency: "BTC",
242 | currency_symbol: :symbol
243 | )
244 |
245 | assert {:ok, "₿1,234,545,656.456789"} =
246 | Cldr.Number.to_string(1_234_545_656.456789,
247 | currency: "BTC",
248 | currency_symbol: :standard
249 | )
250 |
251 | assert {:ok, "XBTC 1,234,545,656.456789"} =
252 | Cldr.Number.to_string(1_234_545_656.456789, currency: "BTC", currency_symbol: "XBTC")
253 | end
254 | end
255 |
256 | test "Formatting a number with standard format in a locale with no RBNF" do
257 | for {_locale, language_tag} <- Cldr.Config.all_language_tags(),
258 | is_nil(language_tag.rbnf_locale_name) do
259 | assert {:ok, _formatted_number} = Cldr.Number.to_string(1234, locale: language_tag)
260 | end
261 | end
262 |
263 | test "that each number system for each locale can format a number with standard format" do
264 | for locale <- TestBackend.Cldr.known_locale_names() do
265 | {:ok, systems} = TestBackend.Cldr.Number.System.number_systems_for(locale)
266 | number_systems = Enum.uniq(Map.keys(systems) ++ Map.values(systems))
267 |
268 | for number_system <- number_systems do
269 | assert {:ok, _} =
270 | TestBackend.Cldr.Number.to_string(123,
271 | locale: locale,
272 | number_system: number_system
273 | )
274 | end
275 | end
276 | end
277 |
278 | test "Formatting when a currency is specified but the format has no currency symbol" do
279 | assert {:ok, _} =
280 | Cldr.Number.to_string(1234,
281 | curency: :CAD,
282 | format: "#,##0.###;-#,##0.###",
283 | locale: :en
284 | )
285 | end
286 |
287 | test "Formatting a currency the standard format if the it has a space before the currency placeholder" do
288 | assert {:ok, "123,45\u00A0kr."} =
289 | TestBackend.Cldr.Number.to_string(123.45, locale: :da, currency: :DKK)
290 | end
291 | end
292 |
--------------------------------------------------------------------------------
/test/number/number_parser_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.Parsing.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "parse numbers" do
5 | assert Cldr.Number.Parser.parse("100", backend: TestBackend.Cldr) == {:ok, 100}
6 | assert Cldr.Number.Parser.parse("100.0", backend: TestBackend.Cldr) == {:ok, 100.0}
7 | assert Cldr.Number.Parser.parse("1_000", backend: TestBackend.Cldr) == {:ok, 1000}
8 | assert Cldr.Number.Parser.parse("+1_000.0", backend: TestBackend.Cldr) == {:ok, 1000.0}
9 | assert Cldr.Number.Parser.parse("-100", backend: TestBackend.Cldr) == {:ok, -100}
10 | assert Cldr.Number.Parser.parse(" 100 ", backend: TestBackend.Cldr) == {:ok, 100}
11 | end
12 |
13 | test "parse numbers with locale other than en" do
14 | assert Cldr.Number.Parser.parse("1.000,00", backend: TestBackend.Cldr, locale: "de") ==
15 | {:ok, 1000.0}
16 | end
17 |
18 | test "scan a number string" do
19 | assert Cldr.Number.Parser.scan("100 australian dollars", backend: TestBackend.Cldr) ==
20 | [100, " australian dollars"]
21 |
22 | assert Cldr.Number.Parser.scan("us dollars 100", backend: TestBackend.Cldr) ==
23 | ["us dollars ", 100]
24 | end
25 |
26 | test "resolving currency and value" do
27 | result =
28 | Cldr.Number.Parser.scan("us dollars 100", backend: TestBackend.Cldr)
29 | |> Cldr.Number.Parser.resolve_currencies(backend: TestBackend.Cldr)
30 |
31 | assert result == [:USD, 100]
32 |
33 | result2 =
34 | Cldr.Number.Parser.scan("$100", backend: TestBackend.Cldr)
35 | |> Cldr.Number.Parser.resolve_currencies(backend: TestBackend.Cldr)
36 |
37 | assert result2 == [:USD, 100]
38 |
39 | result3 =
40 | Cldr.Number.Parser.scan("R100", backend: TestBackend.Cldr)
41 | |> Cldr.Number.Parser.resolve_currencies(backend: TestBackend.Cldr)
42 |
43 | assert result3 == [:ZAR, 100]
44 | end
45 |
46 | test "scanning strings that have symbols in them" do
47 | assert Cldr.Number.Parser.scan("a string, which I think. Well, sometimes not £1_000_000.34") ==
48 | ["a string, which I think. Well, sometimes not £", 1_000_000.34]
49 | end
50 |
51 | test "parse a decimal" do
52 | {:ok, parsed} =
53 | Cldr.Number.Parser.parse("1.000,00",
54 | number: :decimal,
55 | backend: TestBackend.Cldr,
56 | locale: "de"
57 | )
58 |
59 | assert Cldr.Decimal.compare(parsed, Decimal.from_float(1000.0)) == :eq
60 | end
61 |
62 | test "Parse a list of numbers separated by comma" do
63 | assert Cldr.Number.Parser.scan("Here's my number list: 1111, 2222, 3333, 4444, 55,55",
64 | number: :decimal
65 | ) ==
66 | [
67 | "Here's my number list: ",
68 | Decimal.new(1111),
69 | ", ",
70 | Decimal.new(2222),
71 | ", ",
72 | Decimal.new(3333),
73 | ", ",
74 | Decimal.new(4444),
75 | ", ",
76 | Decimal.new(5555)
77 | ]
78 | end
79 |
80 | test "Parsing a locale with a grouping character that is a pop space" do
81 | string = Cldr.Number.to_string!(12345, locale: "fr")
82 | assert Cldr.Number.Parser.scan(string, locale: "fr") == [12345]
83 | assert Cldr.Number.Parser.parse(string, locale: "fr") == {:ok, 12345}
84 | end
85 |
86 | test "Parsing a locale with a grouping character that is a pop space but using 0x20 group char" do
87 | # pop space is 0x202c
88 | assert Cldr.Number.Parser.scan("This with normal space 12 345", locale: "fr") ==
89 | ["This with normal space ", 12345]
90 |
91 | assert Cldr.Number.Parser.scan("This is with pop space 12 345", locale: "fr") ==
92 | ["This is with pop space ", 12345]
93 |
94 | assert Cldr.Number.Parser.scan("This with normal space 12 345", locale: "en") ==
95 | ["This with normal space ", 12, " ", 345]
96 | end
97 |
98 | test "Resolving currencies in a string" do
99 | scanned =
100 | Cldr.Number.Parser.scan(
101 | "Lets try this 123 US dollars, a bunch of US dollars 23 and 345 euros"
102 | )
103 |
104 | assert Cldr.Number.Parser.resolve_currencies(scanned) ==
105 | ["Lets try this ", 123, :USD, ", a bunch of ", :USD, 23, " and ", 345, :EUR]
106 |
107 | scanned =
108 | Cldr.Number.Parser.scan(
109 | "Lets try this 123 US dollars, a bunch of swiss francs 23 and 345 euros"
110 | )
111 |
112 | assert Cldr.Number.Parser.resolve_currencies(scanned) ==
113 | ["Lets try this ", 123, :USD, ", a bunch of ", :CHF, 23, " and ", 345, :EUR]
114 | end
115 |
116 | test "That resolving only happens when there is a non-alpha adjacent char" do
117 | scanned = Cldr.Number.Parser.scan("These are 100 us dollars and also nonswiss francs")
118 |
119 | assert Cldr.Number.Parser.resolve_currencies(scanned) ==
120 | ["These are ", 100, :USD, " and also nonswiss francs"]
121 |
122 | scanned = Cldr.Number.Parser.scan("These are 100 us dollars and also non swiss francs")
123 |
124 | assert Cldr.Number.Parser.resolve_currencies(scanned) ==
125 | ["These are ", 100, :USD, " and also non ", :CHF]
126 |
127 | scanned = Cldr.Number.Parser.scan("These are us dollars 100 and also nonswiss francs")
128 |
129 | assert Cldr.Number.Parser.resolve_currencies(scanned) ==
130 | ["These are ", :USD, 100, " and also nonswiss francs"]
131 |
132 | scanned = Cldr.Number.Parser.scan("These areus dollars 100 and also nonswiss francs")
133 |
134 | assert Cldr.Number.Parser.resolve_currencies(scanned) ==
135 | ["These areus dollars ", 100, " and also nonswiss francs"]
136 | end
137 |
138 | test "That parsing returns the same case as the search for strings" do
139 | scanned = Cldr.Number.Parser.scan("Try ThCA$2is")
140 | assert Cldr.Number.Parser.resolve_currencies(scanned) == [:TRY, " ThCA$", 2, "is"]
141 |
142 | scanned = Cldr.Number.Parser.scan("Try Th CA$2is")
143 | assert Cldr.Number.Parser.resolve_currencies(scanned) == [:TRY, " Th ", :CAD, 2, "is"]
144 | end
145 |
146 | test "Resolve currencies when the string is only whitespace" do
147 | string = [" "]
148 | assert Cldr.Number.Parser.resolve_currencies(string) == string
149 | end
150 | end
151 |
--------------------------------------------------------------------------------
/test/number/number_string_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Number.String.Test do
2 | use ExUnit.Case, async: true
3 |
4 | alias Cldr.Number
5 |
6 | test "that the regexp for latin1 is correct" do
7 | assert Number.String.latin1().source == "([\\x00-\\x7F])"
8 | end
9 |
10 | test "that the regexp for not latin1 is correct" do
11 | assert Number.String.not_latin1().source == "([^\\x00-\\x7F])"
12 | end
13 |
14 | test "that we transliterate a non latin1 character" do
15 | s = Number.String.hex_string("¤")
16 | assert s == "\\x164"
17 | end
18 |
19 | test "that padding a string with a negative count returns the string" do
20 | assert Number.String.pad_leading_zeros("1234", -5) == "1234"
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/number/number_symbol_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Number.Symbol.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "that we can get number symbols for a known locale" do
5 | {:ok, symbols} = TestBackend.Cldr.Number.Symbol.number_symbols_for("en", "latn")
6 |
7 | assert symbols ==
8 | %Cldr.Number.Symbol{
9 | decimal: %{standard: "."},
10 | exponential: "E",
11 | group: %{standard: ","},
12 | infinity: "∞",
13 | list: ";",
14 | minus_sign: "-",
15 | nan: "NaN",
16 | per_mille: "‰",
17 | percent_sign: "%",
18 | plus_sign: "+",
19 | superscripting_exponent: "×",
20 | time_separator: ":"
21 | }
22 | end
23 |
24 | test "that we raise an error if we get minimum digits for an invalid locale" do
25 | assert_raise Cldr.InvalidLanguageError, ~r/The language .* is invalid/, fn ->
26 | TestBackend.Cldr.Number.Format.minimum_grouping_digits_for!("zzzzz")
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/number/number_system_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Number.System.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "that number_system_for with a system name returns" do
5 | {:ok, system} = TestBackend.Cldr.Number.System.number_system_for("en", :latn)
6 | assert system == %{digits: "0123456789", type: :numeric}
7 | end
8 |
9 | test "that number_systems_for raises when the locale is not known" do
10 | assert_raise Cldr.InvalidLanguageError, ~r/The language \"zzz\" is invalid/, fn ->
11 | TestBackend.Cldr.Number.System.number_systems_for!("zzz")
12 | end
13 | end
14 |
15 | test "that number_system_names_for raises when the locale is not known" do
16 | assert_raise Cldr.InvalidLanguageError, ~r/The language .* is invalid/, fn ->
17 | TestBackend.Cldr.Number.System.number_system_names_for!("zzz")
18 | end
19 | end
20 |
21 | test "that number_systems_like returns a list" do
22 | {:ok, likes} = Cldr.Number.System.number_systems_like("en", :latn, TestBackend.Cldr)
23 | assert is_list(likes)
24 | assert Enum.count(likes) > 100
25 | end
26 |
27 | test "that locale u extension number system overrides default" do
28 | assert TestBackend.Cldr.Number.to_string(1234, locale: "th-u-nu-thai") == {:ok, "๑,๒๓๔"}
29 | assert TestBackend.Cldr.Number.to_string(1234, locale: "th-u-nu-latn") == {:ok, "1,234"}
30 | end
31 |
32 | test "that number_system parameter overrides the locale u number system" do
33 | assert TestBackend.Cldr.Number.to_string(1234, locale: "th-u-nu-latn", number_system: :thai) ==
34 | {:ok, "๑,๒๓๔"}
35 | end
36 |
37 | test "that a locale u number system that is not valid for a locale returns an error" do
38 | assert TestBackend.Cldr.Number.to_string(1234, locale: "en-AU-u-nu-thai") ==
39 | {:error,
40 | {Cldr.UnknownNumberSystemError,
41 | "The number system :thai is unknown for the locale named :\"en-AU\". Valid number systems are %{default: :latn, native: :latn}"}}
42 | end
43 |
44 | test "that an invalid nu returns an error" do
45 | assert TestBackend.Cldr.Number.to_string(123, locale: "en-AU-u-nu-xxx") ==
46 | {:error,
47 | {Cldr.LanguageTag.ParseError, "The value \"xxx\" is not valid for the key \"nu\""}}
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/test/number/rbnf_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Rbnf.Test do
2 | use ExUnit.Case, async: true
3 |
4 | alias TestBackend.Cldr
5 |
6 | test "rbnf spellout" do
7 | assert {:ok, "twenty-five thousand three hundred forty"} =
8 | Cldr.Number.to_string(25_340, format: :spellout)
9 | end
10 |
11 | test "rbnf spellout in german" do
12 | assert {:ok, "fünfundzwanzigtausenddreihundertvierzig"} =
13 | Cldr.Number.to_string(25_340, format: :spellout, locale: "de")
14 | end
15 |
16 | test "rbnf spellout in french" do
17 | assert {:ok, "vingt-cinq mille trois cent quarante"} =
18 | Cldr.Number.to_string(25_340, format: :spellout, locale: "fr")
19 | end
20 |
21 | test "rbnf spellout in spanish" do
22 | assert {:ok, "veinticinco mil trescientos cuarenta"} =
23 | Cldr.Number.to_string(25_340, format: :spellout, locale: "es")
24 | end
25 |
26 | test "rbnf spellout in mandarin" do
27 | assert {:ok, "二万五千三百四十"} = Cldr.Number.to_string(25_340, format: :spellout, locale: "zh")
28 | end
29 |
30 | test "rbnf spellout in hebrew" do
31 | assert {:ok, "עשרים וחמישה אלף שלוש מאות ארבעים"} =
32 | Cldr.Number.to_string(25_340, format: :spellout, locale: "he")
33 | end
34 |
35 | test "rbnf spellout ordinal in de" do
36 | assert {:ok, "fünfundzwanzigtausenddreihundertvierzigste"} =
37 | Cldr.Number.to_string(25_340, format: :spellout_ordinal, locale: "de")
38 | end
39 |
40 | test "rbnf spellout ordinal in ar" do
41 | assert {:ok, "خمسة وعشرون ألف وثلاثة مائة وأربعون"} =
42 | Cldr.Number.to_string(25_340, format: :spellout_ordinal_feminine, locale: "ar")
43 |
44 | assert {:ok, "خمسة وعشرون ألف وثلاثة مائة وأربعون"} =
45 | Cldr.Number.to_string(25_340, format: :spellout_ordinal_masculine, locale: "ar")
46 | end
47 |
48 | test "rbnf spellout in thai" do
49 | assert {:ok, "สองหมื่นห้าพันสามร้อยสี่สิบ"} =
50 | Cldr.Number.to_string(25_340, format: :spellout, locale: "th")
51 | end
52 |
53 | test "rbnf spellout ordinal verbose" do
54 | assert {:ok, "one hundred and twenty-three thousand, four hundred and fifty-sixth"} =
55 | Cldr.Number.to_string(123_456, format: :spellout_ordinal_verbose)
56 | end
57 |
58 | test "rbnf ordinal" do
59 | assert {:ok, "123,456th"} = Cldr.Number.to_string(123_456, format: :ordinal)
60 |
61 | assert {:ok, "123 456e"} = Cldr.Number.to_string(123_456, format: :ordinal, locale: "fr")
62 | end
63 |
64 | test "rbnf improper fraction" do
65 | assert Cldr.Rbnf.Spellout.spellout_cardinal_verbose(123.456, "en") ==
66 | "one hundred and twenty-three point four five six"
67 |
68 | assert Cldr.Rbnf.Spellout.spellout_cardinal_verbose(-123.456, "en") ==
69 | "minus one hundred and twenty-three point four five six"
70 |
71 | assert Cldr.Rbnf.Spellout.spellout_cardinal_verbose(-0.456, "en") ==
72 | "minus zero point four five six"
73 |
74 | assert Cldr.Rbnf.Spellout.spellout_cardinal_verbose(0.456, "en") == "zero point four five six"
75 |
76 | assert Cldr.Rbnf.Spellout.spellout_cardinal(0.456, "en") == "zero point four five six"
77 |
78 | assert Cldr.Rbnf.Spellout.spellout_cardinal(0, "en") == "zero"
79 | assert Cldr.Rbnf.Spellout.spellout_ordinal(0, "en") == "zeroth"
80 | assert Cldr.Rbnf.Spellout.spellout_ordinal(0.0, "en") == "0"
81 | assert Cldr.Rbnf.Spellout.spellout_ordinal(0.1, "en") == "0.1"
82 | end
83 |
84 | test "decimal rbnf for decimal integers" do
85 | assert {:ok, "123,456th"} = Cldr.Number.to_string(Decimal.new(123_456), format: :ordinal)
86 |
87 | assert {:ok, "123 456e"} =
88 | Cldr.Number.to_string(
89 | Decimal.new(123_456),
90 | format: :ordinal,
91 | locale: "fr"
92 | )
93 |
94 | assert {:ok, "one hundred and twenty-three thousand, four hundred and fifty-sixth"} =
95 | Cldr.Number.to_string(Decimal.new(123_456), format: :spellout_ordinal_verbose)
96 |
97 | assert {:ok, "twenty-five thousand three hundred forty"} =
98 | Cldr.Number.to_string(Decimal.new(25_340), format: :spellout)
99 |
100 | assert Cldr.Rbnf.Spellout.spellout_cardinal(Decimal.new(0), "en") == "zero"
101 | assert Cldr.Rbnf.Spellout.spellout_ordinal(Decimal.new(0), "en") == "zeroth"
102 | end
103 |
104 | test "roman numerals" do
105 | assert Cldr.Number.to_string(1, format: :roman) == {:ok, "I"}
106 | assert Cldr.Number.to_string(2, format: :roman) == {:ok, "II"}
107 | assert Cldr.Number.to_string(3, format: :roman) == {:ok, "III"}
108 | assert Cldr.Number.to_string(4, format: :roman) == {:ok, "IV"}
109 | assert Cldr.Number.to_string(5, format: :roman) == {:ok, "V"}
110 | assert Cldr.Number.to_string(6, format: :roman) == {:ok, "VI"}
111 | assert Cldr.Number.to_string(7, format: :roman) == {:ok, "VII"}
112 | assert Cldr.Number.to_string(8, format: :roman) == {:ok, "VIII"}
113 | assert Cldr.Number.to_string(9, format: :roman) == {:ok, "IX"}
114 | assert Cldr.Number.to_string(10, format: :roman) == {:ok, "X"}
115 | assert Cldr.Number.to_string(11, format: :roman) == {:ok, "XI"}
116 | assert Cldr.Number.to_string(20, format: :roman) == {:ok, "XX"}
117 | assert Cldr.Number.to_string(50, format: :roman) == {:ok, "L"}
118 | assert Cldr.Number.to_string(90, format: :roman) == {:ok, "XC"}
119 | assert Cldr.Number.to_string(100, format: :roman) == {:ok, "C"}
120 | assert Cldr.Number.to_string(1000, format: :roman) == {:ok, "M"}
121 | assert Cldr.Number.to_string(123, format: :roman) == {:ok, "CXXIII"}
122 | end
123 |
124 | test "no rule is available for number" do
125 | assert Cldr.Rbnf.Spellout.spellout_numbering_year(-24, "zh-Hant") ==
126 | {
127 | :error,
128 | {
129 | Elixir.Cldr.Rbnf.NoRuleForNumber,
130 | "rule group :spellout_numbering_year for locale :\"zh-Hant\" does not know how to process -24"
131 | }
132 | }
133 | end
134 |
135 | test "that rbnf rules lookup fall back to the root locale (und)" do
136 | # implemented in und locale
137 | assert Cldr.Number.to_string(123, format: :digits_ordinal, locale: "de") ==
138 | {:ok, "123."}
139 |
140 | # implemented in en locale
141 | assert Cldr.Number.to_string(123, format: :digits_ordinal, locale: "en") ==
142 | {:ok, "123rd"}
143 | end
144 |
145 | test "RBNF Spellout for spanish" do
146 | assert TestBackend.Cldr.Number.to_string(123.456,
147 | format: :spellout_cardinal_masculine,
148 | locale: :es
149 | ) ==
150 | {:ok, "ciento veintitrés punto cuatro cinco seis"}
151 |
152 | assert TestBackend.Cldr.Number.to_string(123.456,
153 | format: :spellout_cardinal_feminine,
154 | locale: :es
155 | ) ==
156 | {:ok, "ciento veintitrés punto cuatro cinco seis"}
157 | end
158 |
159 | Elixir.Cldr.Rbnf.TestSupport.rbnf_tests(fn name, tests, module, function, locale ->
160 | test name do
161 | Enum.each(unquote(Macro.escape(tests)), fn {test_data, test_result} ->
162 | if apply(unquote(module), unquote(function), [
163 | String.to_integer(test_data),
164 | unquote(Macro.escape(locale))
165 | ]) != test_result do
166 | IO.puts(
167 | "Test is failing on locale #{unquote(locale.requested_locale_name)} for value #{test_data}"
168 | )
169 | end
170 |
171 | assert apply(unquote(module), unquote(function), [
172 | String.to_integer(test_data),
173 | unquote(Macro.escape(locale))
174 | ]) == test_result
175 | end)
176 | end
177 | end)
178 | end
179 |
--------------------------------------------------------------------------------
/test/number/short_formatter_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Number.ShortFormatter.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "Short formatter for a decimal" do
5 | number = Decimal.new("940038.00000000")
6 | assert {:ok, "940K"} = MyApp.Cldr.Number.to_string(number, format: :short)
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/number/significant_digits_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Significant.Digits.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "round 1,239,451 to 3 significant digits and return 1,240,000" do
5 | assert 1_240_000 == Cldr.Math.round_significant(1_239_451, 3)
6 | end
7 |
8 | test "round 12.1257 to 3 significant digits and return 12.1" do
9 | assert 12.1 == Cldr.Math.round_significant(12.1257, 3)
10 | end
11 |
12 | test "round .0681 to 3 significant digits and return .0681" do
13 | assert 0.0681 == Cldr.Math.round_significant(0.0681, 3)
14 | end
15 |
16 | test "round 5 to 3 significant digits and return 5" do
17 | assert 5 == Cldr.Math.round_significant(5, 3)
18 | end
19 |
20 | # From TR35 Section 3.5
21 | test "round 12345 to 3 significant digits and return 12300" do
22 | assert 12300 == Cldr.Math.round_significant(12345, 3)
23 | end
24 |
25 | test "round 0.12345 to 3 significant digits and return 0.123" do
26 | assert 0.123 == Cldr.Math.round_significant(0.12345, 3)
27 | end
28 |
29 | test "round 3.14159 to 4 significant digits and return 3.142" do
30 | assert 3.142 == Cldr.Math.round_significant(3.14159, 4)
31 | end
32 |
33 | test "round 1.23004 to 4 significant digits and return 1.23" do
34 | assert 1.23 == Cldr.Math.round_significant(1.23004, 4)
35 | end
36 |
37 | # Decimal tests
38 | test "round decimal 12345 to 3 significant digits and return 12300" do
39 | assert Cldr.Decimal.reduce(Decimal.new(12300)) ==
40 | Cldr.Math.round_significant(Decimal.new(12345), 3)
41 | end
42 |
43 | test "round decimal 0.12345 to 3 significant digits and return 0.123" do
44 | assert Decimal.new("0.123") == Cldr.Math.round_significant(Decimal.new("0.12345"), 3)
45 | end
46 |
47 | test "round decimal 3.14159 to 4 significant digits and return 3.142" do
48 | assert Decimal.new("3.142") == Cldr.Math.round_significant(Decimal.new("3.14159"), 4)
49 | end
50 |
51 | test "round decimal 1.23004 to 4 significant digits and return 1.23" do
52 | assert Decimal.new("1.23") == Cldr.Math.round_significant(Decimal.new("1.23004"), 4)
53 | end
54 |
55 | test "round negative decimal -1.23004 to 4 significant digits and return 1.23" do
56 | assert Decimal.new("-1.23") == Cldr.Math.round_significant(Decimal.new("-1.23004"), 4)
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/test/number/split_format_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Split.Format.Test do
2 | use ExUnit.Case, async: true
3 |
4 | Enum.each(Cldr.Test.Number.Split.Format.test_data(), fn {format, result} ->
5 | test "that a format splits correctly for #{inspect(format)}" do
6 | regex = Cldr.Number.Format.Compiler.number_match_regex()
7 | assert Regex.named_captures(regex, unquote(format)) == unquote(Macro.escape(result))
8 | end
9 | end)
10 | end
11 |
--------------------------------------------------------------------------------
/test/number/transliteration_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Transliteration.Test do
2 | use ExUnit.Case, async: true
3 |
4 | import ExUnit.CaptureLog
5 |
6 | test "that a dynamic transliteration generates a log message" do
7 | assert capture_log(fn ->
8 | TestBackend.Cldr.Number.Transliterate.transliterate_digits(
9 | "٠١٢٣٤٥٦٧٨٩",
10 | :arab,
11 | :java
12 | )
13 | end) =~ "Transliteration from number system :arab to :java"
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/number/wrapper_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Number.FormatWrapper.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "Wrapping a number" do
5 | assert {:ok, "100"} =
6 | Cldr.Number.to_string(100, wrapper: &NumberWrapper.wrapper/2)
7 | end
8 |
9 | test "Wrapping a currency symbol" do
10 | assert {:ok, "$100.00"} =
11 | Cldr.Number.to_string(100, currency: :USD, wrapper: &NumberWrapper.wrapper/2)
12 | end
13 |
14 | test "Wrapping a currency symbol with inserted space" do
15 | assert {:ok, "CHF 100.00"} =
16 | Cldr.Number.to_string(100, currency: :CHF, wrapper: &NumberWrapper.wrapper/2)
17 | end
18 |
19 | test "Wrapping a percent and permille" do
20 | assert {:ok, "10,000%"} =
21 | Cldr.Number.to_string(100, format: :percent, wrapper: &NumberWrapper.wrapper/2)
22 | end
23 |
24 | test "Wrapping plus and minus" do
25 | assert {:ok, "+100"} =
26 | Cldr.Number.to_string(100, format: "+##", wrapper: &NumberWrapper.wrapper/2)
27 |
28 | assert {:ok, "-100"} =
29 | Cldr.Number.to_string(-100, format: "##", wrapper: &NumberWrapper.wrapper/2)
30 | end
31 |
32 | test "Wrapping a literal" do
33 | assert {:ok, "some string 100"} =
34 | Cldr.Number.to_string(100,
35 | format: "some string ##",
36 | wrapper: &NumberWrapper.wrapper/2
37 | )
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/test/rbnf/rbnf_rule_compiler_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Rbnf.Compiler.Test do
2 | use ExUnit.Case, async: true
3 |
4 | test "that rbnf rules can parse" do
5 | Enum.each(Cldr.Rbnf.all_rule_definitions(TestBackend.Cldr), fn rule ->
6 | parse_result = Cldr.Rbnf.Rule.parse(rule)
7 | assert [{:ok, _}, _] = [parse_result, rule]
8 | end)
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/test/support/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elixir-cldr/cldr_numbers/1fb2f82370c262f83dc348a9781211926ee97fa9/test/support/.gitkeep
--------------------------------------------------------------------------------
/test/support/number_format_test_data.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Test.Number.Format do
2 | def test_data do
3 | [
4 | {1234, "1,234", []},
5 | {1234, "1 234", [locale: "fr"]},
6 | {0.000123456, "0", []},
7 | {-0.000123456, "0", []},
8 |
9 | # Data from http://unicode.org/reports/tr35/tr35-numbers.html
10 |
11 | # Number_Patterns
12 | {1234.567, "1 234,57", [format: "#,##0.##", locale: "fr"]},
13 | {1234.567, "1 234,567", [format: "#,##0.###", locale: "fr"]},
14 | {1234.567, "1234,567", [format: "###0.#####", locale: "fr"]},
15 | {1234.567, "1234,5670", [format: "###0.0000#", locale: "fr"]},
16 | {1234.567, "01234,5670", [format: "00000.0000", locale: "fr"]},
17 | {1234.567, "1 234,57 €", [format: "#,##0.00 ¤", locale: "fr", currency: :EUR]},
18 | {1234.567, "1 235 JPY", [format: "#,##0 ¤", locale: "fr", currency: "JPY"]},
19 | {1234.567, "1 234,57 JPY", [format: "#,##0.00 ¤", locale: "fr", currency: "JPY"]},
20 | {1234.567, "1 234,57 JPY", [format: "#,##0.## ¤", locale: "fr", currency: "JPY"]},
21 | {1234.00, "1 234 JPY", [format: "#,##0.## ¤", locale: "fr", currency: "JPY"]},
22 | {1234, "1 234 JPY", [format: "#,##0.## ¤", locale: "fr", currency: "JPY"]},
23 |
24 | # Fraction grouping
25 | {1234.4353244565, "1234,435 324 456 5", [format: "#,###.###,#########", locale: "pl"]},
26 |
27 | # Special_Pattern_Characters
28 | {3.1415, "3,14", [format: "0.00;-0.00", locale: "fr"]},
29 | {-3.1415, "-3,14", [format: "0.00;-0.00", locale: "fr"]},
30 | {3.1415, "3,14", [format: "0.00;0.00-", locale: "fr"]},
31 | {-3.1415, "3,14-", [format: "0.00;0.00-", locale: "fr"]},
32 | {3.1415, "3,14+", [format: "0.00+;0.00-", locale: "fr"]},
33 | {-3.1415, "3,14-", [format: "0.00+;0.00-", locale: "fr"]},
34 |
35 | # Minimum grouping digits
36 | {1000, "1000", [format: "#,##0.##", locale: "pl"]},
37 | {10000, "10 000", [format: "#,##0.##", locale: "pl"]},
38 |
39 | # Secondary grouping
40 | {1_234_567, "12,34,567", [format: "#,##,###"]},
41 |
42 | # Padding
43 | {123, "$xx123.00", [format: "$*x#,##0.00"]},
44 | {123, "xx$123.00", [format: "*x$#,##0.00"]},
45 | {123, "$123.00xx", [format: "$#,##0.00*x"]},
46 | {1234, "$1,234.00", [format: "$*x#,##0.00"]},
47 | {123, "! $xx123.00", [format: "'!' $*x#,##0.00"]},
48 | {123, "' $xx123.00", [format: "'' $*x#,##0.00"]},
49 |
50 | # Currency
51 | {123.4, "123.40 A$", [format: "#,##0.00 ¤", currency: :AUD]},
52 | {123.4, "123.40 AUD", [format: "#,##0.00 ¤¤", currency: :AUD]},
53 | {123.4, "123.40 Australian dollars", [format: "#,##0.00 ¤¤¤", currency: :AUD]},
54 | {123.4, "123.40 $", [format: "#,##0.00 ¤¤¤¤", currency: :AUD]},
55 | {1234, "A$1,234.00", [currency: :AUD]},
56 | {1234, "COP 1,234.00", [currency: :COP, currency_digits: :iso]},
57 | {1234, "COP 1,234.00", [currency: :COP]},
58 | {1234, "১,২৩৪.০০€", [currency: :EUR, locale: :bn]},
59 |
60 | # Currency where the symbol replaces the decimal separator
61 | {1234, "1234$00", [currency: :CVE, locale: "pt-CV"]},
62 |
63 | # Currency default fractional digits
64 | {1234, "¥1,234", [currency: :JPY]},
65 | {1234, "TND 1,234.000", [currency: :TND]},
66 |
67 | # Currency with varying currency symbol
68 | {1234, "CUSTOM 1,234.00", [currency: :AUD, currency_symbol: "CUSTOM"]},
69 | {1234, "$1,234.00", [currency: :AUD, currency_symbol: :narrow]},
70 | {1234, "A$1,234.00", [currency: :AUD, currency_symbol: :standard]},
71 | {1234, "A$1,234.00", [currency: :AUD, currency_symbol: :symbol]},
72 | {1234, "AUD 1,234.00", [currency: :AUD, currency_symbol: :iso]},
73 | {1234, "1,234.00", [currency: :AUD, currency_symbol: :none]},
74 |
75 | # Rounding
76 | {1234.21, "1,234.20", [format: "#,##0.05"]},
77 | {1234.22, "1,234.20", [format: "#,##0.05"]},
78 | {1234.23, "1,234.25", [format: "#,##0.05"]},
79 | {1234, "1,250", [format: "#,#50"]},
80 |
81 | # Percentage
82 | {0.1234, "12.34%", [format: "#0.0#%"]},
83 | {-0.1234, "-12.34%", [format: "#0.0#%"]},
84 |
85 | # Permille
86 | {0.1234, "123.4‰", [format: "#0.0#‰"]},
87 | {-0.1234, "-123.4‰", [format: "#0.0#‰"]},
88 |
89 | # Negative number format
90 | {-1234, "(1234.00)", [format: "#.00;(#.00)"]},
91 |
92 | # Significant digits format
93 | {12345, "12300", [format: "@@#"]},
94 | {0.12345, "0.123", [format: "@@#"]},
95 | {3.14159, "3.142", [format: "@@##"]},
96 | {1.23004, "1.23", [format: "@@##"]},
97 | {-1.23004, "-1.23", [format: "@@##"]},
98 |
99 | # Test for when padding specified but there is no padding possible
100 | {123_456_789, "123456789", [format: "*x#"]},
101 |
102 | # Scientific formats
103 | {0.1234, "1.234E-1", [format: "#E0"]},
104 | {1.234, "1.234E0", [format: "#E0"]},
105 | {12.34, "1.234E1", [format: "#E0"]},
106 | {123.4, "1.234E2", [format: "#E0"]},
107 | {1234, "1.234E3", [format: "#E0"]},
108 | {1234, "1.234E3", [format: :scientific]},
109 |
110 | # Scientific with exponent sign
111 | {1234, "1.234E+3", [format: "#E+0"]},
112 | {0.000012456, "1.2456E-5", [format: "#E+0"]},
113 |
114 | # Maximum and minimum digits
115 | {1234, "34", [format: "00", maximum_integer_digits: 2]},
116 | {1, "01.00", [format: "00.00"]},
117 |
118 | # Scientific formats with grouping
119 | # {1234, "1.234E3", [format: "#,###E0"]},
120 | # {12.34, "0.012E3", [format: "#,###E0"]}
121 |
122 | # Short formats
123 | {123, "123", [format: :short]},
124 | {1234, "1K", [format: :short]},
125 | {12345, "12K", [format: :short]},
126 | {1234.5, "1K", [format: :short]},
127 | {1234.5, "1.234", [format: :short, locale: "de"]},
128 | {123_456, "123.456", [format: :short, locale: "de"]},
129 | {12_345_678, "12M", [format: :short]},
130 | {1_234_567_890, "1B", [format: :short]},
131 | {1_234_567_890_000, "1T", [format: :short]},
132 | {1234, "1 thousand", [format: :long]},
133 | {1_234_567_890, "1 billion", [format: :long]},
134 | {1234, "$1K", [format: :short, currency: :USD]},
135 | {1234, "ZAR 1K", [format: :short, currency: :ZAR]},
136 | {12345, "12,345 US dollars", [format: :long, currency: :USD]},
137 | {123, "A$123", [format: :short, currency: :AUD]},
138 | {12, "12 Thai baht", [format: :long, currency: :THB]},
139 | {12, "12 bahts thaïlandais", [format: :long, currency: :THB, locale: "fr"]},
140 | {2134, "A$2K", [format: :currency_short, currency: :AUD]},
141 | {2134, "2,134 Australian dollars", [format: :currency_long, currency: :AUD]},
142 | {499_999_999, "500 millions", [format: :long, locale: "fr"]},
143 | {500_000_000, "500 millions", [format: :long, locale: "fr"]},
144 | {9_900_000_000, "10 milliards", [format: :long, locale: "fr"]},
145 | {1_000, "mille", [format: :long, locale: "fr"]},
146 | {1_001, "1 millier", [format: :long, locale: "fr"]},
147 | {1_499, "1 millier", [format: :long, locale: "fr"]},
148 | {1_500, "2 mille", [format: :long, locale: "fr"]},
149 |
150 | # Negative short formats
151 | {1_000_000, "1M", [format: :short]},
152 | {-1_000_000, "-1M", [format: :short]},
153 | {100_000, "100K", [format: :short]},
154 | {-100_000, "-100K", [format: :short]},
155 | {1_000, "1K", [format: :short]},
156 | {-1_000, "-1K", [format: :short]},
157 |
158 | # Notes for future tests in "fr" locale:
159 | # millier is used with the number 1 on front and strictly only for 1000 (1 millier).
160 | # mille is used for anything between 1001 (Mille 1) and 1999 (Mille 999) and with no number before the word mille.
161 | # milles is used for many as the plural form : 3 milles, 4 milles, etc.
162 |
163 | # Formats with literals
164 | {123, "This is a 123.00 format", [format: "This is a #,##0.00 format"]},
165 | {-123, "This is a -123.00 format", [format: "This is a #,##0.00 format"]},
166 | {0.1, "This is a 10% format", [format: "This is a #,##0% format"]},
167 | {-0.1, "This is a -10% format", [format: "This is a #,##0% format"]},
168 |
169 | # Specify a currency in the locale
170 | {123.4, "123.40 $", [format: "#,##0.00 ¤", locale: "en-AU-u-cu-aud"]},
171 | {123.4, "123.40 USD", [format: "#,##0.00 ¤", locale: "en-AU-u-cu-aud", currency: :USD]},
172 |
173 | # Specify accounting or currency in the locale doesn't change the format
174 | {-123.4, "-123.40", [format: :currency, locale: "en-AU-u-cu-aud-cf-standard"]},
175 | {-123.4, "-123.40", [format: :currency, locale: "en-AU-u-cu-aud-cf-account"]},
176 | ]
177 | |> merge_crypto_tests(System.otp_release())
178 | end
179 |
180 | # Formatting digital tokens (Crypto currencies)
181 | # Because OTP 28 introduces a new re module, OTP 28 will correctly
182 | # detect that "₿" is a symbol whereas early versions do not.
183 |
184 | def merge_crypto_tests(tests, version) when version >= "28" do
185 | tests ++ [
186 | {1_234_545_656.456789, "₿1,234,545,656.456789", [currency: "BTC"]},
187 | {1_234_545_656.456789, "₿1B", [format: :short, currency: "BTC"]},
188 | {1_234_545_656.456789, "1,234,545,656.456789 Bitcoin", [format: :long, currency: "BTC"]}
189 | ]
190 | end
191 |
192 | def merge_crypto_tests(tests, _version) do
193 | tests ++ [
194 | {1_234_545_656.456789, "₿ 1,234,545,656.456789", [currency: "BTC"]},
195 | {1_234_545_656.456789, "₿ 1B", [format: :short, currency: "BTC"]},
196 | {1_234_545_656.456789, "1,234,545,656.456789 Bitcoin", [format: :long, currency: "BTC"]}
197 | ]
198 | end
199 | end
200 |
--------------------------------------------------------------------------------
/test/support/rbnf/fa/rbnf_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "SpelloutRules": {
3 | "spellout-cardinal": {
4 | "-1141": "منفی یک هزار و صد و چهل و یک",
5 | "-1142": "منفی یک هزار و صد و چهل و دو",
6 | "-1143": "منفی یک هزار و صد و چهل و سه",
7 | "-100": "منفی صد",
8 | "-75": "منفی هفتاد و پنج",
9 | "-50": "منفی پنجاه",
10 | "-24": "منفی بیست و چهار",
11 | "0": "صفر",
12 | "1": "یک",
13 | "2": "دو",
14 | "3": "سه",
15 | "4": "چهار",
16 | "5": "پنج",
17 | "6": "شش",
18 | "7": "هفت",
19 | "8": "هشت",
20 | "9": "نه",
21 | "10": "ده",
22 | "11": "یازده",
23 | "12": "دوازده",
24 | "13": "سیزده",
25 | "14": "چهارده",
26 | "15": "پانزده",
27 | "16": "شانزده",
28 | "17": "هفده",
29 | "18": "هجده",
30 | "19": "نوزده",
31 | "20": "بیست",
32 | "21": "بیست و یک",
33 | "22": "بیست و دو",
34 | "23": "بیست و سه",
35 | "24": "بیست و چهار",
36 | "25": "بیست و پنج",
37 | "26": "بیست و شش",
38 | "27": "بیست و هفت",
39 | "28": "بیست و هشت",
40 | "29": "بیست و نه",
41 | "30": "سی",
42 | "31": "سی و یک",
43 | "32": "سی و دو",
44 | "33": "سی و سه",
45 | "34": "سی و چهار",
46 | "35": "سی و پنج",
47 | "36": "سی و شش",
48 | "37": "سی و هفت",
49 | "38": "سی و هشت",
50 | "39": "سی و نه",
51 | "40": "چهل",
52 | "41": "چهل و یک",
53 | "42": "چهل و دو",
54 | "43": "چهل و سه",
55 | "44": "چهل و چهار",
56 | "45": "چهل و پنج",
57 | "46": "چهل و شش",
58 | "47": "چهل و هفت",
59 | "48": "چهل و هشت",
60 | "49": "چهل و نه",
61 | "50": "پنجاه",
62 | "51": "پنجاه و یک",
63 | "52": "پنجاه و دو",
64 | "53": "پنجاه و سه",
65 | "54": "پنجاه و چهار",
66 | "55": "پنجاه و پنج",
67 | "56": "پنجاه و شش",
68 | "57": "پنجاه و هفت",
69 | "58": "پنجاه و هشت",
70 | "59": "پنجاه و نه",
71 | "60": "شصت",
72 | "61": "شصت و یک",
73 | "62": "شصت و دو",
74 | "63": "شصت و سه",
75 | "64": "شصت و چهار",
76 | "65": "شصت و پنج",
77 | "66": "شصت و شش",
78 | "67": "شصت و هفت",
79 | "68": "شصت و هشت",
80 | "69": "شصت و نه",
81 | "70": "هفتاد",
82 | "71": "هفتاد و یک",
83 | "72": "هفتاد و دو",
84 | "73": "هفتاد و سه",
85 | "74": "هفتاد و چهار",
86 | "75": "هفتاد و پنج",
87 | "76": "هفتاد و شش",
88 | "77": "هفتاد و هفت",
89 | "78": "هفتاد و هشت",
90 | "79": "هفتاد و نه",
91 | "80": "هشتاد",
92 | "81": "هشتاد و یک",
93 | "82": "هشتاد و دو",
94 | "83": "هشتاد و سه",
95 | "84": "هشتاد و چهار",
96 | "85": "هشتاد و پنج",
97 | "86": "هشتاد و شش",
98 | "87": "هشتاد و هفت",
99 | "88": "هشتاد و هشت",
100 | "89": "هشتاد و نه",
101 | "90": "نود",
102 | "91": "نود و یک",
103 | "92": "نود و دو",
104 | "93": "نود و سه",
105 | "94": "نود و چهار",
106 | "95": "نود و پنج",
107 | "96": "نود و شش",
108 | "97": "نود و هفت",
109 | "98": "نود و هشت",
110 | "99": "نود و نه",
111 | "100": "صد",
112 | "321": "سیصد و بیست و یک",
113 | "322": "سیصد و بیست و دو",
114 | "323": "سیصد و بیست و سه",
115 | "1141": "یک هزار و صد و چهل و یک",
116 | "1142": "یک هزار و صد و چهل و دو",
117 | "1143": "یک هزار و صد و چهل و سه",
118 | "10311": "ده هزار و سیصد و یازده",
119 | "138400": "صد و سی و هشت هزار و چهارصد"
120 | },
121 | "spellout-numbering": {
122 | "-1141": "منفی یک هزار و صد و چهل و یک",
123 | "-1142": "منفی یک هزار و صد و چهل و دو",
124 | "-1143": "منفی یک هزار و صد و چهل و سه",
125 | "-100": "منفی صد",
126 | "-75": "منفی هفتاد و پنج",
127 | "-50": "منفی پنجاه",
128 | "-24": "منفی بیست و چهار",
129 | "0": "صفر",
130 | "1": "یک",
131 | "2": "دو",
132 | "3": "سه",
133 | "4": "چهار",
134 | "5": "پنج",
135 | "6": "شش",
136 | "7": "هفت",
137 | "8": "هشت",
138 | "9": "نه",
139 | "10": "ده",
140 | "11": "یازده",
141 | "12": "دوازده",
142 | "13": "سیزده",
143 | "14": "چهارده",
144 | "15": "پانزده",
145 | "16": "شانزده",
146 | "17": "هفده",
147 | "18": "هجده",
148 | "19": "نوزده",
149 | "20": "بیست",
150 | "21": "بیست و یک",
151 | "22": "بیست و دو",
152 | "23": "بیست و سه",
153 | "24": "بیست و چهار",
154 | "25": "بیست و پنج",
155 | "26": "بیست و شش",
156 | "27": "بیست و هفت",
157 | "28": "بیست و هشت",
158 | "29": "بیست و نه",
159 | "30": "سی",
160 | "31": "سی و یک",
161 | "32": "سی و دو",
162 | "33": "سی و سه",
163 | "34": "سی و چهار",
164 | "35": "سی و پنج",
165 | "36": "سی و شش",
166 | "37": "سی و هفت",
167 | "38": "سی و هشت",
168 | "39": "سی و نه",
169 | "40": "چهل",
170 | "41": "چهل و یک",
171 | "42": "چهل و دو",
172 | "43": "چهل و سه",
173 | "44": "چهل و چهار",
174 | "45": "چهل و پنج",
175 | "46": "چهل و شش",
176 | "47": "چهل و هفت",
177 | "48": "چهل و هشت",
178 | "49": "چهل و نه",
179 | "50": "پنجاه",
180 | "51": "پنجاه و یک",
181 | "52": "پنجاه و دو",
182 | "53": "پنجاه و سه",
183 | "54": "پنجاه و چهار",
184 | "55": "پنجاه و پنج",
185 | "56": "پنجاه و شش",
186 | "57": "پنجاه و هفت",
187 | "58": "پنجاه و هشت",
188 | "59": "پنجاه و نه",
189 | "60": "شصت",
190 | "61": "شصت و یک",
191 | "62": "شصت و دو",
192 | "63": "شصت و سه",
193 | "64": "شصت و چهار",
194 | "65": "شصت و پنج",
195 | "66": "شصت و شش",
196 | "67": "شصت و هفت",
197 | "68": "شصت و هشت",
198 | "69": "شصت و نه",
199 | "70": "هفتاد",
200 | "71": "هفتاد و یک",
201 | "72": "هفتاد و دو",
202 | "73": "هفتاد و سه",
203 | "74": "هفتاد و چهار",
204 | "75": "هفتاد و پنج",
205 | "76": "هفتاد و شش",
206 | "77": "هفتاد و هفت",
207 | "78": "هفتاد و هشت",
208 | "79": "هفتاد و نه",
209 | "80": "هشتاد",
210 | "81": "هشتاد و یک",
211 | "82": "هشتاد و دو",
212 | "83": "هشتاد و سه",
213 | "84": "هشتاد و چهار",
214 | "85": "هشتاد و پنج",
215 | "86": "هشتاد و شش",
216 | "87": "هشتاد و هفت",
217 | "88": "هشتاد و هشت",
218 | "89": "هشتاد و نه",
219 | "90": "نود",
220 | "91": "نود و یک",
221 | "92": "نود و دو",
222 | "93": "نود و سه",
223 | "94": "نود و چهار",
224 | "95": "نود و پنج",
225 | "96": "نود و شش",
226 | "97": "نود و هفت",
227 | "98": "نود و هشت",
228 | "99": "نود و نه",
229 | "100": "صد",
230 | "321": "سیصد و بیست و یک",
231 | "322": "سیصد و بیست و دو",
232 | "323": "سیصد و بیست و سه",
233 | "1141": "یک هزار و صد و چهل و یک",
234 | "1142": "یک هزار و صد و چهل و دو",
235 | "1143": "یک هزار و صد و چهل و سه",
236 | "10311": "ده هزار و سیصد و یازده",
237 | "138400": "صد و سی و هشت هزار و چهارصد"
238 | },
239 | "spellout-numbering-year": {
240 | "-1141": "منفی یک هزار و صد و چهل و یک",
241 | "-1142": "منفی یک هزار و صد و چهل و دو",
242 | "-1143": "منفی یک هزار و صد و چهل و سه",
243 | "-100": "منفی صد",
244 | "-75": "منفی هفتاد و پنج",
245 | "-50": "منفی پنجاه",
246 | "-24": "منفی بیست و چهار",
247 | "0": "صفر",
248 | "1": "یک",
249 | "2": "دو",
250 | "3": "سه",
251 | "4": "چهار",
252 | "5": "پنج",
253 | "6": "شش",
254 | "7": "هفت",
255 | "8": "هشت",
256 | "9": "نه",
257 | "10": "ده",
258 | "11": "یازده",
259 | "12": "دوازده",
260 | "13": "سیزده",
261 | "14": "چهارده",
262 | "15": "پانزده",
263 | "16": "شانزده",
264 | "17": "هفده",
265 | "18": "هجده",
266 | "19": "نوزده",
267 | "20": "بیست",
268 | "21": "بیست و یک",
269 | "22": "بیست و دو",
270 | "23": "بیست و سه",
271 | "24": "بیست و چهار",
272 | "25": "بیست و پنج",
273 | "26": "بیست و شش",
274 | "27": "بیست و هفت",
275 | "28": "بیست و هشت",
276 | "29": "بیست و نه",
277 | "30": "سی",
278 | "31": "سی و یک",
279 | "32": "سی و دو",
280 | "33": "سی و سه",
281 | "34": "سی و چهار",
282 | "35": "سی و پنج",
283 | "36": "سی و شش",
284 | "37": "سی و هفت",
285 | "38": "سی و هشت",
286 | "39": "سی و نه",
287 | "40": "چهل",
288 | "41": "چهل و یک",
289 | "42": "چهل و دو",
290 | "43": "چهل و سه",
291 | "44": "چهل و چهار",
292 | "45": "چهل و پنج",
293 | "46": "چهل و شش",
294 | "47": "چهل و هفت",
295 | "48": "چهل و هشت",
296 | "49": "چهل و نه",
297 | "50": "پنجاه",
298 | "51": "پنجاه و یک",
299 | "52": "پنجاه و دو",
300 | "53": "پنجاه و سه",
301 | "54": "پنجاه و چهار",
302 | "55": "پنجاه و پنج",
303 | "56": "پنجاه و شش",
304 | "57": "پنجاه و هفت",
305 | "58": "پنجاه و هشت",
306 | "59": "پنجاه و نه",
307 | "60": "شصت",
308 | "61": "شصت و یک",
309 | "62": "شصت و دو",
310 | "63": "شصت و سه",
311 | "64": "شصت و چهار",
312 | "65": "شصت و پنج",
313 | "66": "شصت و شش",
314 | "67": "شصت و هفت",
315 | "68": "شصت و هشت",
316 | "69": "شصت و نه",
317 | "70": "هفتاد",
318 | "71": "هفتاد و یک",
319 | "72": "هفتاد و دو",
320 | "73": "هفتاد و سه",
321 | "74": "هفتاد و چهار",
322 | "75": "هفتاد و پنج",
323 | "76": "هفتاد و شش",
324 | "77": "هفتاد و هفت",
325 | "78": "هفتاد و هشت",
326 | "79": "هفتاد و نه",
327 | "80": "هشتاد",
328 | "81": "هشتاد و یک",
329 | "82": "هشتاد و دو",
330 | "83": "هشتاد و سه",
331 | "84": "هشتاد و چهار",
332 | "85": "هشتاد و پنج",
333 | "86": "هشتاد و شش",
334 | "87": "هشتاد و هفت",
335 | "88": "هشتاد و هشت",
336 | "89": "هشتاد و نه",
337 | "90": "نود",
338 | "91": "نود و یک",
339 | "92": "نود و دو",
340 | "93": "نود و سه",
341 | "94": "نود و چهار",
342 | "95": "نود و پنج",
343 | "96": "نود و شش",
344 | "97": "نود و هفت",
345 | "98": "نود و هشت",
346 | "99": "نود و نه",
347 | "100": "صد",
348 | "321": "سیصد و بیست و یک",
349 | "322": "سیصد و بیست و دو",
350 | "323": "سیصد و بیست و سه",
351 | "1141": "یک هزار و صد و چهل و یک",
352 | "1142": "یک هزار و صد و چهل و دو",
353 | "1143": "یک هزار و صد و چهل و سه",
354 | "10311": "ده هزار و سیصد و یازده",
355 | "138400": "صد و سی و هشت هزار و چهارصد"
356 | }
357 | },
358 | "OrdinalRules": {
359 | "digits-ordinal": {
360 | "-1141": "−۱٬۱۴۱.",
361 | "-1142": "−۱٬۱۴۲.",
362 | "-1143": "−۱٬۱۴۳.",
363 | "-100": "−۱۰۰.",
364 | "-75": "−۷۵.",
365 | "-50": "−۵۰.",
366 | "-24": "−۲۴.",
367 | "0": "۰.",
368 | "1": "۱.",
369 | "2": "۲.",
370 | "3": "۳.",
371 | "4": "۴.",
372 | "5": "۵.",
373 | "6": "۶.",
374 | "7": "۷.",
375 | "8": "۸.",
376 | "9": "۹.",
377 | "10": "۱۰.",
378 | "11": "۱۱.",
379 | "12": "۱۲.",
380 | "13": "۱۳.",
381 | "14": "۱۴.",
382 | "15": "۱۵.",
383 | "16": "۱۶.",
384 | "17": "۱۷.",
385 | "18": "۱۸.",
386 | "19": "۱۹.",
387 | "20": "۲۰.",
388 | "21": "۲۱.",
389 | "22": "۲۲.",
390 | "23": "۲۳.",
391 | "24": "۲۴.",
392 | "25": "۲۵.",
393 | "26": "۲۶.",
394 | "27": "۲۷.",
395 | "28": "۲۸.",
396 | "29": "۲۹.",
397 | "30": "۳۰.",
398 | "31": "۳۱.",
399 | "32": "۳۲.",
400 | "33": "۳۳.",
401 | "34": "۳۴.",
402 | "35": "۳۵.",
403 | "36": "۳۶.",
404 | "37": "۳۷.",
405 | "38": "۳۸.",
406 | "39": "۳۹.",
407 | "40": "۴۰.",
408 | "41": "۴۱.",
409 | "42": "۴۲.",
410 | "43": "۴۳.",
411 | "44": "۴۴.",
412 | "45": "۴۵.",
413 | "46": "۴۶.",
414 | "47": "۴۷.",
415 | "48": "۴۸.",
416 | "49": "۴۹.",
417 | "50": "۵۰.",
418 | "51": "۵۱.",
419 | "52": "۵۲.",
420 | "53": "۵۳.",
421 | "54": "۵۴.",
422 | "55": "۵۵.",
423 | "56": "۵۶.",
424 | "57": "۵۷.",
425 | "58": "۵۸.",
426 | "59": "۵۹.",
427 | "60": "۶۰.",
428 | "61": "۶۱.",
429 | "62": "۶۲.",
430 | "63": "۶۳.",
431 | "64": "۶۴.",
432 | "65": "۶۵.",
433 | "66": "۶۶.",
434 | "67": "۶۷.",
435 | "68": "۶۸.",
436 | "69": "۶۹.",
437 | "70": "۷۰.",
438 | "71": "۷۱.",
439 | "72": "۷۲.",
440 | "73": "۷۳.",
441 | "74": "۷۴.",
442 | "75": "۷۵.",
443 | "76": "۷۶.",
444 | "77": "۷۷.",
445 | "78": "۷۸.",
446 | "79": "۷۹.",
447 | "80": "۸۰.",
448 | "81": "۸۱.",
449 | "82": "۸۲.",
450 | "83": "۸۳.",
451 | "84": "۸۴.",
452 | "85": "۸۵.",
453 | "86": "۸۶.",
454 | "87": "۸۷.",
455 | "88": "۸۸.",
456 | "89": "۸۹.",
457 | "90": "۹۰.",
458 | "91": "۹۱.",
459 | "92": "۹۲.",
460 | "93": "۹۳.",
461 | "94": "۹۴.",
462 | "95": "۹۵.",
463 | "96": "۹۶.",
464 | "97": "۹۷.",
465 | "98": "۹۸.",
466 | "99": "۹۹.",
467 | "100": "۱۰۰.",
468 | "321": "۳۲۱.",
469 | "322": "۳۲۲.",
470 | "323": "۳۲۳.",
471 | "1141": "۱٬۱۴۱.",
472 | "1142": "۱٬۱۴۲.",
473 | "1143": "۱٬۱۴۳.",
474 | "10311": "۱۰٬۳۱۱.",
475 | "138400": "۱۳۸٬۴۰۰."
476 | }
477 | }
478 | }
--------------------------------------------------------------------------------
/test/support/rbnf/ga/rbnf_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "SpelloutRules": {
3 | "spellout-cardinal": {
4 | "-1141": "míneas míle, céad daichead a haon",
5 | "-1142": "míneas míle, céad daichead a dó",
6 | "-1143": "míneas míle, céad daichead a trí",
7 | "-100": "míneas céad",
8 | "-75": "míneas seachtó a cúig",
9 | "-50": "míneas caoga",
10 | "-24": "míneas fiche a ceathair",
11 | "0": "a náid",
12 | "1": "a haon",
13 | "2": "a dó",
14 | "3": "a trí",
15 | "4": "a ceathair",
16 | "5": "a cúig",
17 | "6": "a sé",
18 | "7": "a seacht",
19 | "8": "a hocht",
20 | "9": "a naoi",
21 | "10": "a deich",
22 | "11": "a haon déag",
23 | "12": "a dó dhéag",
24 | "13": "a trí déag",
25 | "14": "a ceathair déag",
26 | "15": "a cúig déag",
27 | "16": "a sé déag",
28 | "17": "a seacht déag",
29 | "18": "a hocht déag",
30 | "19": "a naoi déag",
31 | "20": "fiche",
32 | "21": "fiche a haon",
33 | "22": "fiche a dó",
34 | "23": "fiche a trí",
35 | "24": "fiche a ceathair",
36 | "25": "fiche a cúig",
37 | "26": "fiche a sé",
38 | "27": "fiche a seacht",
39 | "28": "fiche a hocht",
40 | "29": "fiche a naoi",
41 | "30": "tríocha",
42 | "31": "tríocha a haon",
43 | "32": "tríocha a dó",
44 | "33": "tríocha a trí",
45 | "34": "tríocha a ceathair",
46 | "35": "tríocha a cúig",
47 | "36": "tríocha a sé",
48 | "37": "tríocha a seacht",
49 | "38": "tríocha a hocht",
50 | "39": "tríocha a naoi",
51 | "40": "daichead",
52 | "41": "daichead a haon",
53 | "42": "daichead a dó",
54 | "43": "daichead a trí",
55 | "44": "daichead a ceathair",
56 | "45": "daichead a cúig",
57 | "46": "daichead a sé",
58 | "47": "daichead a seacht",
59 | "48": "daichead a hocht",
60 | "49": "daichead a naoi",
61 | "50": "caoga",
62 | "51": "caoga a haon",
63 | "52": "caoga a dó",
64 | "53": "caoga a trí",
65 | "54": "caoga a ceathair",
66 | "55": "caoga a cúig",
67 | "56": "caoga a sé",
68 | "57": "caoga a seacht",
69 | "58": "caoga a hocht",
70 | "59": "caoga a naoi",
71 | "60": "seasca",
72 | "61": "seasca a haon",
73 | "62": "seasca a dó",
74 | "63": "seasca a trí",
75 | "64": "seasca a ceathair",
76 | "65": "seasca a cúig",
77 | "66": "seasca a sé",
78 | "67": "seasca a seacht",
79 | "68": "seasca a hocht",
80 | "69": "seasca a naoi",
81 | "70": "seachtó",
82 | "71": "seachtó a haon",
83 | "72": "seachtó a dó",
84 | "73": "seachtó a trí",
85 | "74": "seachtó a ceathair",
86 | "75": "seachtó a cúig",
87 | "76": "seachtó a sé",
88 | "77": "seachtó a seacht",
89 | "78": "seachtó a hocht",
90 | "79": "seachtó a naoi",
91 | "80": "ochtó",
92 | "81": "ochtó a haon",
93 | "82": "ochtó a dó",
94 | "83": "ochtó a trí",
95 | "84": "ochtó a ceathair",
96 | "85": "ochtó a cúig",
97 | "86": "ochtó a sé",
98 | "87": "ochtó a seacht",
99 | "88": "ochtó a hocht",
100 | "89": "ochtó a naoi",
101 | "90": "nócha",
102 | "91": "nócha a haon",
103 | "92": "nócha a dó",
104 | "93": "nócha a trí",
105 | "94": "nócha a ceathair",
106 | "95": "nócha a cúig",
107 | "96": "nócha a sé",
108 | "97": "nócha a seacht",
109 | "98": "nócha a hocht",
110 | "99": "nócha a naoi",
111 | "100": "céad",
112 | "321": "trí chéad fiche a haon",
113 | "322": "trí chéad fiche a dó",
114 | "323": "trí chéad fiche a trí",
115 | "1141": "míle, céad daichead a haon",
116 | "1142": "míle, céad daichead a dó",
117 | "1143": "míle, céad daichead a trí",
118 | "10311": "deich míle, trí chéad a haon déag",
119 | "138400": "céad tríocha is ocht míle, ceithre chéad"
120 | },
121 | "spellout-numbering": {
122 | "-1141": "míneas míle, céad daichead a haon",
123 | "-1142": "míneas míle, céad daichead a dó",
124 | "-1143": "míneas míle, céad daichead a trí",
125 | "-100": "míneas céad",
126 | "-75": "míneas seachtó a cúig",
127 | "-50": "míneas caoga",
128 | "-24": "míneas fiche a ceathair",
129 | "0": "a náid",
130 | "1": "a haon",
131 | "2": "a dó",
132 | "3": "a trí",
133 | "4": "a ceathair",
134 | "5": "a cúig",
135 | "6": "a sé",
136 | "7": "a seacht",
137 | "8": "a hocht",
138 | "9": "a naoi",
139 | "10": "a deich",
140 | "11": "a haon déag",
141 | "12": "a dó dhéag",
142 | "13": "a trí déag",
143 | "14": "a ceathair déag",
144 | "15": "a cúig déag",
145 | "16": "a sé déag",
146 | "17": "a seacht déag",
147 | "18": "a hocht déag",
148 | "19": "a naoi déag",
149 | "20": "fiche",
150 | "21": "fiche a haon",
151 | "22": "fiche a dó",
152 | "23": "fiche a trí",
153 | "24": "fiche a ceathair",
154 | "25": "fiche a cúig",
155 | "26": "fiche a sé",
156 | "27": "fiche a seacht",
157 | "28": "fiche a hocht",
158 | "29": "fiche a naoi",
159 | "30": "tríocha",
160 | "31": "tríocha a haon",
161 | "32": "tríocha a dó",
162 | "33": "tríocha a trí",
163 | "34": "tríocha a ceathair",
164 | "35": "tríocha a cúig",
165 | "36": "tríocha a sé",
166 | "37": "tríocha a seacht",
167 | "38": "tríocha a hocht",
168 | "39": "tríocha a naoi",
169 | "40": "daichead",
170 | "41": "daichead a haon",
171 | "42": "daichead a dó",
172 | "43": "daichead a trí",
173 | "44": "daichead a ceathair",
174 | "45": "daichead a cúig",
175 | "46": "daichead a sé",
176 | "47": "daichead a seacht",
177 | "48": "daichead a hocht",
178 | "49": "daichead a naoi",
179 | "50": "caoga",
180 | "51": "caoga a haon",
181 | "52": "caoga a dó",
182 | "53": "caoga a trí",
183 | "54": "caoga a ceathair",
184 | "55": "caoga a cúig",
185 | "56": "caoga a sé",
186 | "57": "caoga a seacht",
187 | "58": "caoga a hocht",
188 | "59": "caoga a naoi",
189 | "60": "seasca",
190 | "61": "seasca a haon",
191 | "62": "seasca a dó",
192 | "63": "seasca a trí",
193 | "64": "seasca a ceathair",
194 | "65": "seasca a cúig",
195 | "66": "seasca a sé",
196 | "67": "seasca a seacht",
197 | "68": "seasca a hocht",
198 | "69": "seasca a naoi",
199 | "70": "seachtó",
200 | "71": "seachtó a haon",
201 | "72": "seachtó a dó",
202 | "73": "seachtó a trí",
203 | "74": "seachtó a ceathair",
204 | "75": "seachtó a cúig",
205 | "76": "seachtó a sé",
206 | "77": "seachtó a seacht",
207 | "78": "seachtó a hocht",
208 | "79": "seachtó a naoi",
209 | "80": "ochtó",
210 | "81": "ochtó a haon",
211 | "82": "ochtó a dó",
212 | "83": "ochtó a trí",
213 | "84": "ochtó a ceathair",
214 | "85": "ochtó a cúig",
215 | "86": "ochtó a sé",
216 | "87": "ochtó a seacht",
217 | "88": "ochtó a hocht",
218 | "89": "ochtó a naoi",
219 | "90": "nócha",
220 | "91": "nócha a haon",
221 | "92": "nócha a dó",
222 | "93": "nócha a trí",
223 | "94": "nócha a ceathair",
224 | "95": "nócha a cúig",
225 | "96": "nócha a sé",
226 | "97": "nócha a seacht",
227 | "98": "nócha a hocht",
228 | "99": "nócha a naoi",
229 | "100": "céad",
230 | "321": "trí chéad fiche a haon",
231 | "322": "trí chéad fiche a dó",
232 | "323": "trí chéad fiche a trí",
233 | "1141": "míle, céad daichead a haon",
234 | "1142": "míle, céad daichead a dó",
235 | "1143": "míle, céad daichead a trí",
236 | "10311": "deich míle, trí chéad a haon déag",
237 | "138400": "céad tríocha is ocht míle, ceithre chéad"
238 | },
239 | "spellout-numbering-year": {
240 | "-1141": "míneas aon déag daichead a haon",
241 | "-1142": "míneas aon déag daichead a dó",
242 | "-1143": "míneas aon déag daichead a trí",
243 | "-100": "míneas céad",
244 | "-75": "míneas seachtó a cúig",
245 | "-50": "míneas caoga",
246 | "-24": "míneas fiche a ceathair",
247 | "0": "a náid",
248 | "1": "a haon",
249 | "2": "a dó",
250 | "3": "a trí",
251 | "4": "a ceathair",
252 | "5": "a cúig",
253 | "6": "a sé",
254 | "7": "a seacht",
255 | "8": "a hocht",
256 | "9": "a naoi",
257 | "10": "a deich",
258 | "11": "a haon déag",
259 | "12": "a dó dhéag",
260 | "13": "a trí déag",
261 | "14": "a ceathair déag",
262 | "15": "a cúig déag",
263 | "16": "a sé déag",
264 | "17": "a seacht déag",
265 | "18": "a hocht déag",
266 | "19": "a naoi déag",
267 | "20": "fiche",
268 | "21": "fiche a haon",
269 | "22": "fiche a dó",
270 | "23": "fiche a trí",
271 | "24": "fiche a ceathair",
272 | "25": "fiche a cúig",
273 | "26": "fiche a sé",
274 | "27": "fiche a seacht",
275 | "28": "fiche a hocht",
276 | "29": "fiche a naoi",
277 | "30": "tríocha",
278 | "31": "tríocha a haon",
279 | "32": "tríocha a dó",
280 | "33": "tríocha a trí",
281 | "34": "tríocha a ceathair",
282 | "35": "tríocha a cúig",
283 | "36": "tríocha a sé",
284 | "37": "tríocha a seacht",
285 | "38": "tríocha a hocht",
286 | "39": "tríocha a naoi",
287 | "40": "daichead",
288 | "41": "daichead a haon",
289 | "42": "daichead a dó",
290 | "43": "daichead a trí",
291 | "44": "daichead a ceathair",
292 | "45": "daichead a cúig",
293 | "46": "daichead a sé",
294 | "47": "daichead a seacht",
295 | "48": "daichead a hocht",
296 | "49": "daichead a naoi",
297 | "50": "caoga",
298 | "51": "caoga a haon",
299 | "52": "caoga a dó",
300 | "53": "caoga a trí",
301 | "54": "caoga a ceathair",
302 | "55": "caoga a cúig",
303 | "56": "caoga a sé",
304 | "57": "caoga a seacht",
305 | "58": "caoga a hocht",
306 | "59": "caoga a naoi",
307 | "60": "seasca",
308 | "61": "seasca a haon",
309 | "62": "seasca a dó",
310 | "63": "seasca a trí",
311 | "64": "seasca a ceathair",
312 | "65": "seasca a cúig",
313 | "66": "seasca a sé",
314 | "67": "seasca a seacht",
315 | "68": "seasca a hocht",
316 | "69": "seasca a naoi",
317 | "70": "seachtó",
318 | "71": "seachtó a haon",
319 | "72": "seachtó a dó",
320 | "73": "seachtó a trí",
321 | "74": "seachtó a ceathair",
322 | "75": "seachtó a cúig",
323 | "76": "seachtó a sé",
324 | "77": "seachtó a seacht",
325 | "78": "seachtó a hocht",
326 | "79": "seachtó a naoi",
327 | "80": "ochtó",
328 | "81": "ochtó a haon",
329 | "82": "ochtó a dó",
330 | "83": "ochtó a trí",
331 | "84": "ochtó a ceathair",
332 | "85": "ochtó a cúig",
333 | "86": "ochtó a sé",
334 | "87": "ochtó a seacht",
335 | "88": "ochtó a hocht",
336 | "89": "ochtó a naoi",
337 | "90": "nócha",
338 | "91": "nócha a haon",
339 | "92": "nócha a dó",
340 | "93": "nócha a trí",
341 | "94": "nócha a ceathair",
342 | "95": "nócha a cúig",
343 | "96": "nócha a sé",
344 | "97": "nócha a seacht",
345 | "98": "nócha a hocht",
346 | "99": "nócha a naoi",
347 | "100": "céad",
348 | "321": "trí chéad fiche a haon",
349 | "322": "trí chéad fiche a dó",
350 | "323": "trí chéad fiche a trí",
351 | "1141": "aon déag daichead a haon",
352 | "1142": "aon déag daichead a dó",
353 | "1143": "aon déag daichead a trí",
354 | "10311": "deich míle, trí chéad a haon déag",
355 | "138400": "céad tríocha is ocht míle, ceithre chéad"
356 | }
357 | },
358 | "OrdinalRules": {
359 | "digits-ordinal": {
360 | "-1141": "−1,141ú",
361 | "-1142": "−1,142ú",
362 | "-1143": "−1,143ú",
363 | "-100": "−100ú",
364 | "-75": "−75ú",
365 | "-50": "−50ú",
366 | "-24": "−24ú",
367 | "0": "0ú",
368 | "1": "1ú",
369 | "2": "2ú",
370 | "3": "3ú",
371 | "4": "4ú",
372 | "5": "5ú",
373 | "6": "6ú",
374 | "7": "7ú",
375 | "8": "8ú",
376 | "9": "9ú",
377 | "10": "10ú",
378 | "11": "11ú",
379 | "12": "12ú",
380 | "13": "13ú",
381 | "14": "14ú",
382 | "15": "15ú",
383 | "16": "16ú",
384 | "17": "17ú",
385 | "18": "18ú",
386 | "19": "19ú",
387 | "20": "20ú",
388 | "21": "21ú",
389 | "22": "22ú",
390 | "23": "23ú",
391 | "24": "24ú",
392 | "25": "25ú",
393 | "26": "26ú",
394 | "27": "27ú",
395 | "28": "28ú",
396 | "29": "29ú",
397 | "30": "30ú",
398 | "31": "31ú",
399 | "32": "32ú",
400 | "33": "33ú",
401 | "34": "34ú",
402 | "35": "35ú",
403 | "36": "36ú",
404 | "37": "37ú",
405 | "38": "38ú",
406 | "39": "39ú",
407 | "40": "40ú",
408 | "41": "41ú",
409 | "42": "42ú",
410 | "43": "43ú",
411 | "44": "44ú",
412 | "45": "45ú",
413 | "46": "46ú",
414 | "47": "47ú",
415 | "48": "48ú",
416 | "49": "49ú",
417 | "50": "50ú",
418 | "51": "51ú",
419 | "52": "52ú",
420 | "53": "53ú",
421 | "54": "54ú",
422 | "55": "55ú",
423 | "56": "56ú",
424 | "57": "57ú",
425 | "58": "58ú",
426 | "59": "59ú",
427 | "60": "60ú",
428 | "61": "61ú",
429 | "62": "62ú",
430 | "63": "63ú",
431 | "64": "64ú",
432 | "65": "65ú",
433 | "66": "66ú",
434 | "67": "67ú",
435 | "68": "68ú",
436 | "69": "69ú",
437 | "70": "70ú",
438 | "71": "71ú",
439 | "72": "72ú",
440 | "73": "73ú",
441 | "74": "74ú",
442 | "75": "75ú",
443 | "76": "76ú",
444 | "77": "77ú",
445 | "78": "78ú",
446 | "79": "79ú",
447 | "80": "80ú",
448 | "81": "81ú",
449 | "82": "82ú",
450 | "83": "83ú",
451 | "84": "84ú",
452 | "85": "85ú",
453 | "86": "86ú",
454 | "87": "87ú",
455 | "88": "88ú",
456 | "89": "89ú",
457 | "90": "90ú",
458 | "91": "91ú",
459 | "92": "92ú",
460 | "93": "93ú",
461 | "94": "94ú",
462 | "95": "95ú",
463 | "96": "96ú",
464 | "97": "97ú",
465 | "98": "98ú",
466 | "99": "99ú",
467 | "100": "100ú",
468 | "321": "321ú",
469 | "322": "322ú",
470 | "323": "323ú",
471 | "1141": "1,141ú",
472 | "1142": "1,142ú",
473 | "1143": "1,143ú",
474 | "10311": "10,311ú",
475 | "138400": "138,400ú"
476 | }
477 | }
478 | }
--------------------------------------------------------------------------------
/test/support/rbnf_test_support.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Rbnf.TestSupport do
2 | def rbnf_tests(fun) when is_function(fun) do
3 |
4 | # Possible bug being surfaced by :pl
5 | locales = TestBackend.Cldr.known_locale_names()
6 |
7 | _force_atom_instantiation = [OrdinalRules, CardinalRules]
8 |
9 | for locale_name <- locales do
10 | json_data_file = "./test/support/rbnf/#{locale_name}/rbnf_test.json"
11 | file_data = File.read(json_data_file)
12 |
13 | case file_data do
14 | {:error, _} ->
15 | :no_such_locale_test_file
16 |
17 | {:ok, json_string} ->
18 | json_data =
19 | json_string
20 | |> Jason.decode!()
21 |
22 | locale = TestBackend.Cldr.Locale.new!(locale_name)
23 |
24 | if Cldr.known_rbnf_locale_name?(locale_name, TestBackend.Cldr) do
25 | rbnf_data = Cldr.Rbnf.for_locale!(locale)
26 |
27 | Enum.each(Map.keys(json_data), fn rule_group ->
28 | if rbnf_data[String.to_existing_atom(rule_group)] do
29 | module =
30 | "Elixir.TestBackend.Cldr.Rbnf.#{rule_group}"
31 | |> String.replace("Rules", "")
32 | |> String.to_atom()
33 |
34 | Enum.each(json_data[rule_group], fn {rule_set, tests} ->
35 | function =
36 | rule_set
37 | |> String.replace("-", "_")
38 | |> String.to_atom()
39 |
40 | name =
41 | "#{module}.#{function} for locale #{inspect(locale_name)}"
42 | |> String.replace("_", "-")
43 |
44 | fun.(name, tests, module, function, locale)
45 | end)
46 | end
47 | end)
48 | end
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/test/support/split_format_test_data.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.Test.Number.Split.Format do
2 | def test_data do
3 | [
4 | {"#",
5 | %{"exponent_digits" => "", "exponent_sign" => "", "fraction" => "", "integer" => "#"}},
6 | {"###",
7 | %{
8 | "exponent_digits" => "",
9 | "exponent_sign" => "",
10 | "fraction" => "",
11 | "integer" => "###"
12 | }},
13 | {"###.0",
14 | %{
15 | "exponent_digits" => "",
16 | "exponent_sign" => "",
17 | "fraction" => "0",
18 | "integer" => "###"
19 | }},
20 | {"#00.0",
21 | %{
22 | "exponent_digits" => "",
23 | "exponent_sign" => "",
24 | "fraction" => "0",
25 | "integer" => "#00"
26 | }},
27 | {"#00.0E0",
28 | %{
29 | "exponent_digits" => "0",
30 | "exponent_sign" => "",
31 | "fraction" => "0",
32 | "integer" => "#00"
33 | }},
34 | {"#00.0E+0",
35 | %{
36 | "exponent_digits" => "0",
37 | "exponent_sign" => "+",
38 | "fraction" => "0",
39 | "integer" => "#00"
40 | }},
41 | {"#00.0E-0",
42 | %{
43 | "exponent_digits" => "0",
44 | "exponent_sign" => "-",
45 | "fraction" => "0",
46 | "integer" => "#00"
47 | }},
48 | {"#00E-0",
49 | %{
50 | "exponent_digits" => "0",
51 | "exponent_sign" => "-",
52 | "fraction" => "",
53 | "integer" => "#00"
54 | }}
55 | ]
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/test/support/test_backend.ex:
--------------------------------------------------------------------------------
1 | defmodule TestBackend.Cldr do
2 | use Cldr,
3 | default_locale: "en",
4 | locales: :all,
5 | precompile_transliterations: [{:latn, :arab}, {:arab, :thai}, {:arab, :latn}],
6 | providers: [Cldr.Number],
7 | suppress_warnings: true
8 | end
9 |
10 | defmodule NoDoc.Cldr do
11 | use Cldr,
12 | generate_docs: false,
13 | suppress_warnings: true,
14 | default_currency_format: nil
15 | end
16 |
17 | defmodule DefaultCurrencyFormat do
18 | use Cldr,
19 | default_locale: "en",
20 | locales: ["en", "fr"],
21 | providers: [Cldr.Number],
22 | default_currency_format: :currency
23 | end
24 |
25 | defmodule DefaultAccountingFormat do
26 | use Cldr,
27 | default_locale: "en",
28 | locales: ["en", "fr"],
29 | providers: [Cldr.Number],
30 | default_currency_format: :accounting
31 | end
32 |
--------------------------------------------------------------------------------
/test/support/wrapper.ex:
--------------------------------------------------------------------------------
1 | defmodule NumberWrapper do
2 | def wrapper(string, tag) do
3 | "<#{tag}>" <> string <> "<#{tag}>"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/sync_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Cldr.SyncTest do
2 | use ExUnit.Case
3 |
4 | test "that we raise if no default backend" do
5 | :ok = Application.delete_env(:ex_cldr, :default_backend)
6 |
7 | assert_raise Cldr.NoDefaultBackendError, fn ->
8 | Cldr.Number.to_string(1234)
9 | end
10 |
11 | :ok = Application.put_env(:ex_cldr, :default_backend, TestBackend.Cldr)
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start(trace: "--trace" in System.argv(), timeout: 120_000)
2 |
3 | Code.require_file("test/support/number_format_test_data.exs")
4 | Code.require_file("test/support/split_format_test_data.exs")
5 | Code.require_file("test/support/rbnf_test_support.exs")
6 |
--------------------------------------------------------------------------------