├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── elm-package.json ├── src ├── Intl │ ├── Collator.elm │ ├── Currency.elm │ ├── DateTimeFormat.elm │ ├── Locale.elm │ ├── NumberFormat.elm │ ├── PluralRules.elm │ └── TimeZone.elm └── Native │ └── Intl │ ├── Collator.js │ ├── DateTimeFormat.js │ ├── Locale.js │ ├── NumberFormat.js │ ├── PluralRules.js │ └── TimeZone.js └── tests ├── Test ├── Collator.elm ├── Currency.elm ├── DateTimeFormat.elm ├── Locale.elm ├── NumberFormat.elm ├── PluralRules.elm └── TimeZone.elm └── elm-package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.js] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore system files 2 | 3 | # OS X 4 | .DS_Store 5 | .Spotlight-V100 6 | .Trashes 7 | ._* 8 | 9 | # Win 10 | Thumbs.db 11 | Desktop.ini 12 | 13 | # vim 14 | *~ 15 | .swp 16 | .*.sw[a-z] 17 | Session.vim 18 | 19 | # Elm 20 | elm-stuff 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | - stable 5 | install: 6 | - npm install -g standard elm@0.18 elm-test@0.18 7 | - elm-package install -y 8 | - pushd tests && elm-package install -y && popd 9 | script: 10 | - standard src/**/*.js && elm-test 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Andrew VanWagoner 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | 15 | * Neither the name of Andrew VanWagoner nor the names of other 16 | contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-intl [![Build Status](https://travis-ci.org/vanwagonet/elm-intl.svg)](https://travis-ci.org/vanwagonet/elm-intl) 2 | 3 | This library contains bindings to the [Intl][Intl] ECMAScript 4 | Internationalization API. Including `Collator`, `DateTimeFormat`, 5 | `NumberFormat` and `PluralRules`. 6 | 7 | For environments that do not include the Internationalization API, you will need 8 | to load a [polyfill][polyfill]. Node.js < 4 and Safari < 10 are known to need 9 | the polyfill. Some more recent browsers and environments support part of `Intl` 10 | but lack support for `PluralRules` - if you need this (and not the rest) you can 11 | use just the [intl-pluralrules][intl-pluralrules] polyfill. 12 | 13 | 14 | ## Usage 15 | 16 | First get a `Locale` to use: 17 | 18 | ```elm 19 | import Intl.Locale exposing (Locale, fromLanguageTag, en) 20 | import Maybe exposing (withDefault) 21 | 22 | appLocale : Locale 23 | appLocale = 24 | fromLanguageTag "pt-BR" 25 | |> withDefault en 26 | ``` 27 | 28 | You may then use it to create a `Collator`, 29 | `DateTimeFormat`, or `NumberFormat`: 30 | 31 | ```elm 32 | import Intl.Collator as Collator 33 | import List exposing (sortWith) 34 | 35 | localeCompare : String -> String -> Order 36 | localeCompare = 37 | Collator.fromLocale appLocale 38 | |> Collator.compare 39 | 40 | localeSort : List String -> List String 41 | localeSort = 42 | sortWith localeCompare 43 | ``` 44 | 45 | ```elm 46 | import Intl.DateTimeFormat as DateTimeFormat 47 | 48 | formatDate : Date -> String 49 | formatDate = 50 | DateTimeFormat.fromLocale appLocale 51 | |> DateTimeFormat.format 52 | ``` 53 | 54 | ```elm 55 | import Intl.NumberFormat as NumberFormat 56 | 57 | formatNumber : number -> String 58 | formatNumber = 59 | NumberFormat.fromLocale appLocale 60 | |> NumberFormat.format 61 | ``` 62 | 63 | All of the Intl objects can be configured with more detailed options using 64 | `fromOptions`. See the full [docs][docs] for more details. 65 | 66 | 67 | [Intl]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl 68 | [polyfill]: https://github.com/andyearnshaw/Intl.js 69 | [intl-pluralrules]: https://github.com/eemeli/intl-pluralrules/ 70 | [docs]: http://elm-directory.herokuapp.com/package/vanwagonet/elm-intl/ 71 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "summary": "Bindings to JavaScript Internationalization API", 4 | "repository": "https://github.com/vanwagonet/elm-intl.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Intl.Collator", 11 | "Intl.Currency", 12 | "Intl.DateTimeFormat", 13 | "Intl.Locale", 14 | "Intl.NumberFormat", 15 | "Intl.PluralRules", 16 | "Intl.TimeZone" 17 | ], 18 | "native-modules": true, 19 | "dependencies": { 20 | "elm-lang/core": "5.1.1 <= v < 6.0.0" 21 | }, 22 | "elm-version": "0.18.0 <= v < 0.19.0" 23 | } 24 | -------------------------------------------------------------------------------- /src/Intl/Collator.elm: -------------------------------------------------------------------------------- 1 | module Intl.Collator 2 | exposing 3 | ( Collator 4 | , fromLocale 5 | , fromOptions 6 | , compare 7 | , Options 8 | , Usage(Sort, Search) 9 | , Sensitivity(Base, Accent, Case, Variant) 10 | , CaseFirst(Upper, Lower, Default) 11 | , defaults 12 | , resolvedOptions 13 | , supportedLocalesOf 14 | ) 15 | 16 | {-| A library for comparing strings in a language sensitve way. This module 17 | binds to [Intl.Collator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator). 18 | Note that the [Intl.js](https://github.com/andyearnshaw/Intl.js/) polyfill does 19 | *not* include `Intl.Collator`. In environments without `Intl.Collator` this 20 | library will fall back to `String.prototype.localeCompare`, which will not 21 | respect the locale and options passed in. 22 | 23 | # Create 24 | @docs Collator, fromLocale, fromOptions 25 | 26 | # Comparing Strings 27 | @docs compare 28 | 29 | # Support and options 30 | 31 | Not all environments will support all languages and options. These functions 32 | help determine what is supported, and what options a particular Collator will 33 | use. 34 | 35 | @docs Options, Usage, Sensitivity, CaseFirst, defaults, resolvedOptions, supportedLocalesOf 36 | -} 37 | 38 | import Native.Intl.Collator 39 | import Intl.Locale exposing (Locale, en) 40 | 41 | 42 | {-| A Collator object, for comparing strings in a language sensitive way. 43 | -} 44 | type Collator 45 | = Collator 46 | 47 | 48 | {-| Create a Collator using rules from the specified language 49 | 50 | compare (fromLocale "pt-BR") "Tudo" "Bem" 51 | -} 52 | fromLocale : Locale -> Collator 53 | fromLocale = 54 | Native.Intl.Collator.fromLocale 55 | 56 | 57 | {-| Create a Collator using rules from the language and other options. 58 | 59 | let 60 | naturalCompare = fromOptions 61 | { locale = Locale.en 62 | , usage = Sort 63 | , sensitivity = Base 64 | , ignorePunctuation = True 65 | , numeric = True 66 | , caseFirst = Default 67 | } 68 | |> compare 69 | in 70 | naturalCompare "123" "25" -- GT 71 | -} 72 | fromOptions : Options -> Collator 73 | fromOptions = 74 | Native.Intl.Collator.fromOptions 75 | 76 | 77 | {-| Compare two Strings according to the sort order of the Collator. 78 | 79 | compare (fromLocale Locale.en) "123" "25" -- LT 80 | -} 81 | compare : Collator -> String -> String -> Order 82 | compare = 83 | Native.Intl.Collator.compare 84 | 85 | 86 | {-| An Options record, containing the possible settings for a Collator object. 87 | -} 88 | type alias Options = 89 | { locale : Locale 90 | , usage : Usage 91 | , sensitivity : Sensitivity 92 | , ignorePunctuation : Bool 93 | , numeric : Bool 94 | , caseFirst : CaseFirst 95 | } 96 | 97 | 98 | {-| Whether the comparison is for sorting or for searching for matching strings. 99 | -} 100 | type Usage 101 | = Sort 102 | | Search 103 | 104 | 105 | {-| Which differences in the strings should lead to non-zero result values. 106 | Possible values are: 107 | 108 | * Base: Only strings that differ in base letters compare as unequal. Examples: 109 | `a ≠ b`, `a = á`, `a = A`. 110 | * Accent: Only strings that differ in base letters or accents and other 111 | diacritic marks compare as unequal. Examples: `a ≠ b`, `a ≠ á`, `a = A`. 112 | * Case: Only strings that differ in base letters or case compare as unequal. 113 | Examples: `a ≠ b`, `a = á`, `a ≠ A`. 114 | * Variant: Strings that differ in base letters, accents and other diacritic 115 | marks, or case compare as unequal. Other differences may also be taken into 116 | consideration. Examples: `a ≠ b`, `a ≠ á`, `a ≠ A`. 117 | -} 118 | type Sensitivity 119 | = Base 120 | | Accent 121 | | Case 122 | | Variant 123 | 124 | 125 | {-| Whether upper case or lower case should sort first, or use the default order 126 | for the language. 127 | -} 128 | type CaseFirst 129 | = Upper 130 | | Lower 131 | | Default 132 | 133 | 134 | {-| Returns the default options. This is helpful if you only care to change a 135 | few options. 136 | 137 | options : Options 138 | options = 139 | { defaults | sensitivity = Base } 140 | -} 141 | defaults : Options 142 | defaults = 143 | { locale = en 144 | , usage = Sort 145 | , sensitivity = Variant 146 | , ignorePunctuation = False 147 | , numeric = False 148 | , caseFirst = Default 149 | } 150 | 151 | 152 | {-| Returns the locale and collation options computed when the Collator was 153 | created. 154 | 155 | if (resolvedOptions collator).numeric then 156 | "Sorts numbers naturally" 157 | else 158 | "Sorts numbers lexically" 159 | -} 160 | resolvedOptions : Collator -> Options 161 | resolvedOptions = 162 | Native.Intl.Collator.resolvedOptions 163 | 164 | 165 | {-| Returns a list from the provided languages that are supported without having 166 | to fall back to the runtime's default language. 167 | 168 | case fromLanguageTag "qya" of 169 | Just elvish -> 170 | if isEmpty (supportedLocalesOf [ elvish ]) then 171 | "I can't sort Elvish text" 172 | else 173 | "Tolkien is #1" 174 | Nothing -> 175 | "You shall not pass" 176 | -} 177 | supportedLocalesOf : List Locale -> List Locale 178 | supportedLocalesOf = 179 | Native.Intl.Collator.supportedLocalesOf 180 | -------------------------------------------------------------------------------- /src/Intl/Currency.elm: -------------------------------------------------------------------------------- 1 | module Intl.Currency 2 | exposing 3 | ( Currency 4 | , fromCurrencyCode 5 | , toCurrencyCode 6 | , usd 7 | , eur 8 | , jpy 9 | , gbp 10 | , chf 11 | , cad 12 | ) 13 | 14 | {-| A Currency is used to the display numeric data as money. Note that 15 | `NumberFormat` does *not* do exchange currency conversion, it only uses the 16 | currency for displaying symbols and other conventions to mark the number as 17 | money. 18 | 19 | @docs Currency, fromCurrencyCode, toCurrencyCode 20 | 21 | # Predefined Currencies 22 | 23 | A few currencies have been pre-defined for convenience. 24 | 25 | @docs usd, eur, jpy, gbp, chf, cad 26 | -} 27 | 28 | import Regex exposing (regex, contains) 29 | import String exposing (toUpper) 30 | 31 | 32 | {-| The Currency type holds a valid ISO 4217 currency code. 33 | -} 34 | type Currency 35 | = Currency String 36 | 37 | 38 | {-| Checks the string as a valid currency code, and returns a currency if it is. 39 | 40 | -- Going back to the Gold Standard 41 | fromCurrencyCode "XAU" 42 | 43 | If a string is passed that is not a valid currency code, `Nothing` will be 44 | returned. 45 | 46 | Note that any 3-letter string will work, but if it isn't a defined currency 47 | code, then the formatting will have no symbol or currency name. It will just use 48 | the code you provided. 49 | -} 50 | fromCurrencyCode : String -> Maybe Currency 51 | fromCurrencyCode code = 52 | if contains (regex "^[A-Za-z]{3}$") code then 53 | Just (Currency (toUpper code)) 54 | else 55 | Nothing 56 | 57 | 58 | {-| Gets the string currency code from a Currency 59 | 60 | toCurrencyCode Currency.eur == "EUR" 61 | -} 62 | toCurrencyCode : Currency -> String 63 | toCurrencyCode currency = 64 | case currency of 65 | Currency tag -> 66 | tag 67 | 68 | 69 | {-| United States Dollar 70 | -} 71 | usd : Currency 72 | usd = 73 | Currency "USD" 74 | 75 | 76 | {-| Euro 77 | -} 78 | eur : Currency 79 | eur = 80 | Currency "EUR" 81 | 82 | 83 | {-| Japanese Yen 84 | -} 85 | jpy : Currency 86 | jpy = 87 | Currency "JPY" 88 | 89 | 90 | {-| Pound Sterling 91 | -} 92 | gbp : Currency 93 | gbp = 94 | Currency "GBP" 95 | 96 | 97 | {-| Swiss Franc 98 | -} 99 | chf : Currency 100 | chf = 101 | Currency "CHF" 102 | 103 | 104 | {-| Canadian Dollar 105 | -} 106 | cad : Currency 107 | cad = 108 | Currency "CAD" 109 | -------------------------------------------------------------------------------- /src/Intl/DateTimeFormat.elm: -------------------------------------------------------------------------------- 1 | module Intl.DateTimeFormat 2 | exposing 3 | ( DateTimeFormat 4 | , fromLocale 5 | , fromOptions 6 | , format 7 | , Options 8 | , NameStyle(NarrowName, ShortName, LongName, OmitName) 9 | , NumberStyle(NumericNumber, TwoDigitNumber, OmitNumber) 10 | , MonthStyle(NarrowMonth, ShortMonth, LongMonth, NumericMonth, TwoDigitMonth, OmitMonth) 11 | , TimeZoneStyle(ShortTimeZone, LongTimeZone, OmitTimeZone) 12 | , defaults 13 | , resolvedOptions 14 | , supportedLocalesOf 15 | ) 16 | 17 | {-| A library for formatting dates in a language sensitve way. This module 18 | binds to [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat). 19 | 20 | # Create 21 | @docs DateTimeFormat, fromLocale, fromOptions 22 | 23 | # Formatting Dates 24 | @docs format 25 | 26 | # Support and options 27 | 28 | Not all environments will support all languages and options. These functions 29 | help determine what is supported, and what options a particular DateTimeFormat will 30 | use. 31 | 32 | @docs Options, NameStyle, NumberStyle, MonthStyle, TimeZoneStyle, defaults, resolvedOptions, supportedLocalesOf 33 | -} 34 | 35 | import Native.Intl.DateTimeFormat 36 | import Intl.Locale exposing (Locale, en) 37 | import Intl.TimeZone exposing (TimeZone) 38 | import Maybe exposing (Maybe) 39 | import Date exposing (Date) 40 | 41 | 42 | {-| A DateTimeFormat object, for formatting dates in a language sensitive way. 43 | -} 44 | type DateTimeFormat 45 | = DateTimeFormat 46 | 47 | 48 | {-| Create a DateTimeFormat using rules from the specified language 49 | 50 | format (fromLocale "pt-BR") christmas 51 | -- "25/12/2016" 52 | -} 53 | fromLocale : Locale -> DateTimeFormat 54 | fromLocale = 55 | Native.Intl.DateTimeFormat.fromLocale 56 | 57 | 58 | {-| Create a DateTimeFormat using rules from the language and other options. 59 | 60 | let 61 | formatTime = fromOptions 62 | { defaults | 63 | , locale = Locale.de 64 | , hour = NumericNumber 65 | , minute = TwoDigitNumber 66 | } 67 | |> format 68 | in 69 | formatTime elevenPM -- 23:00 70 | -} 71 | fromOptions : Options -> DateTimeFormat 72 | fromOptions = 73 | Native.Intl.DateTimeFormat.fromOptions 74 | 75 | 76 | {-| Format a Date according to the rules of the DateTimeFormat. 77 | 78 | format (fromLocale Locale.en) Date.now 79 | -} 80 | format : DateTimeFormat -> Date -> String 81 | format = 82 | Native.Intl.DateTimeFormat.format 83 | 84 | 85 | {-| An Options record, containing the possible settings for a DateTimeFormat 86 | object. 87 | -} 88 | type alias Options = 89 | { locale : Locale 90 | , timeZone : Maybe TimeZone 91 | , hour12 : Maybe Bool 92 | , weekday : NameStyle 93 | , era : NameStyle 94 | , year : NumberStyle 95 | , month : MonthStyle 96 | , day : NumberStyle 97 | , hour : NumberStyle 98 | , minute : NumberStyle 99 | , second : NumberStyle 100 | , timeZoneName : TimeZoneStyle 101 | } 102 | 103 | 104 | {-| Style options for the date parts that can be names. 105 | -} 106 | type NameStyle 107 | = NarrowName 108 | | ShortName 109 | | LongName 110 | | OmitName 111 | 112 | 113 | {-| Style options for the date parts that can be numbers. 114 | -} 115 | type NumberStyle 116 | = NumericNumber 117 | | TwoDigitNumber 118 | | OmitNumber 119 | 120 | 121 | {-| Style options for the month. 122 | -} 123 | type MonthStyle 124 | = NarrowMonth 125 | | ShortMonth 126 | | LongMonth 127 | | NumericMonth 128 | | TwoDigitMonth 129 | | OmitMonth 130 | 131 | 132 | {-| Style options for the timeZoneName. 133 | -} 134 | type TimeZoneStyle 135 | = ShortTimeZone 136 | | LongTimeZone 137 | | OmitTimeZone 138 | 139 | 140 | {-| Returns the default options. This is helpful if you only care to change a 141 | few options. If all the date time parts are left ommitted, then `year`, `month`, 142 | and `day` will be assumed numeric. 143 | 144 | options : Options 145 | options = 146 | { defaults | weekday = ShortName } 147 | -} 148 | defaults : Options 149 | defaults = 150 | { locale = en 151 | , timeZone = Nothing 152 | , hour12 = Nothing 153 | , weekday = OmitName 154 | , era = OmitName 155 | , year = OmitNumber 156 | , month = OmitMonth 157 | , day = OmitNumber 158 | , hour = OmitNumber 159 | , minute = OmitNumber 160 | , second = OmitNumber 161 | , timeZoneName = OmitTimeZone 162 | } 163 | 164 | 165 | {-| Returns the locale and formatting options computed when the DateTimeFormat 166 | was created. 167 | 168 | if (resolvedOptions dateTimeFormat).hour12 then 169 | "AM/PM" 170 | else 171 | "Military Time" 172 | -} 173 | resolvedOptions : DateTimeFormat -> Options 174 | resolvedOptions = 175 | Native.Intl.DateTimeFormat.resolvedOptions 176 | 177 | 178 | {-| Returns a list from the provided languages that are supported without having 179 | to fall back to the runtime's default language. 180 | 181 | case fromLanguageTag "tlh" of 182 | Just klingon -> 183 | if isEmpty (supportedLocalesOf [ klingon ]) then 184 | "I can't sort Klingon text" 185 | else 186 | "Make it so, Number One" 187 | Nothing -> 188 | "Khaaaaan!" 189 | -} 190 | supportedLocalesOf : List Locale -> List Locale 191 | supportedLocalesOf = 192 | Native.Intl.DateTimeFormat.supportedLocalesOf 193 | -------------------------------------------------------------------------------- /src/Intl/Locale.elm: -------------------------------------------------------------------------------- 1 | module Intl.Locale 2 | exposing 3 | ( Locale 4 | , fromLanguageTag 5 | , toLanguageTag 6 | , en 7 | , zhCN 8 | , zhTW 9 | , fr 10 | , de 11 | , it 12 | , ja 13 | , ko 14 | ) 15 | 16 | {-| A Locale represents a BCP 47 language tag including optional script, region, 17 | variant, and extensions. 18 | 19 | @docs Locale, fromLanguageTag, toLanguageTag 20 | 21 | # Predefined Locales 22 | 23 | A few locales have been pre-defined for convenience. 24 | 25 | @docs en, zhCN, zhTW, fr, de, it, ja, ko 26 | -} 27 | 28 | import Native.Intl.Locale 29 | 30 | 31 | {-| The Locale type holds a valid BCP 47 language tag. 32 | -} 33 | type Locale 34 | = Locale String 35 | 36 | 37 | {-| Checks the string as a valid language tag, and returns a locale if it is. 38 | 39 | Unicode extensions can be added to the language tag that customize the behavior. 40 | Although any explicit options used in `fromOptions` will take precendence over 41 | the locale extensions. 42 | 43 | -- German Phone Book sorting 44 | fromLanguageTag "de-u-co-phonebk" 45 | 46 | If a string is passed that is not a valid BCP 47 language tag, `Nothing` will be 47 | returned. 48 | -} 49 | fromLanguageTag : String -> Maybe Locale 50 | fromLanguageTag = 51 | Native.Intl.Locale.fromLanguageTag 52 | 53 | 54 | {-| Gets the string language tag from a Locale 55 | 56 | toLanguageTag Locale.zhCN == "zh-CN" 57 | -} 58 | toLanguageTag : Locale -> String 59 | toLanguageTag locale = 60 | case locale of 61 | Locale tag -> 62 | tag 63 | 64 | 65 | {-| English 66 | -} 67 | en : Locale 68 | en = 69 | Locale "en" 70 | 71 | 72 | {-| Simplified Chinese 73 | -} 74 | zhCN : Locale 75 | zhCN = 76 | Locale "zh-CN" 77 | 78 | 79 | {-| Traditional Chinese 80 | -} 81 | zhTW : Locale 82 | zhTW = 83 | Locale "zh-TW" 84 | 85 | 86 | {-| French 87 | -} 88 | fr : Locale 89 | fr = 90 | Locale "fr" 91 | 92 | 93 | {-| German 94 | -} 95 | de : Locale 96 | de = 97 | Locale "de" 98 | 99 | 100 | {-| Italian 101 | -} 102 | it : Locale 103 | it = 104 | Locale "it" 105 | 106 | 107 | {-| Japanese 108 | -} 109 | ja : Locale 110 | ja = 111 | Locale "ja" 112 | 113 | 114 | {-| Korean 115 | -} 116 | ko : Locale 117 | ko = 118 | Locale "ko" 119 | -------------------------------------------------------------------------------- /src/Intl/NumberFormat.elm: -------------------------------------------------------------------------------- 1 | module Intl.NumberFormat 2 | exposing 3 | ( NumberFormat 4 | , fromLocale 5 | , fromOptions 6 | , format 7 | , Options 8 | , Style(PercentStyle, CurrencyStyle, DecimalStyle) 9 | , CurrencyDisplay(CurrencyCode, CurrencyName, CurrencySymbol) 10 | , defaults 11 | , resolvedOptions 12 | , supportedLocalesOf 13 | ) 14 | 15 | {-| A library for formatting numbers in a language sensitve way. This module 16 | binds to [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat). 17 | 18 | # Create 19 | @docs NumberFormat, fromLocale, fromOptions 20 | 21 | # Formatting Dates 22 | @docs format 23 | 24 | # Support and options 25 | 26 | Not all environments will support all languages and options. These functions 27 | help determine what is supported, and what options a particular NumberFormat will 28 | use. 29 | 30 | @docs Options, Style, CurrencyDisplay, defaults, resolvedOptions, supportedLocalesOf 31 | -} 32 | 33 | import Native.Intl.NumberFormat 34 | import Intl.Locale exposing (Locale, en) 35 | import Intl.Currency exposing (Currency, usd) 36 | import Maybe exposing (Maybe) 37 | 38 | 39 | {-| A NumberFormat object, for formatting numbers in a language sensitive way. 40 | -} 41 | type NumberFormat 42 | = NumberFormat 43 | 44 | 45 | {-| Create a NumberFormat using rules from the specified language 46 | 47 | format (fromLocale "ar") 42 48 | -- "٤٢" 49 | -} 50 | fromLocale : Locale -> NumberFormat 51 | fromLocale = 52 | Native.Intl.NumberFormat.fromLocale 53 | 54 | 55 | {-| Create a NumberFormat using rules from the language and other options. 56 | 57 | let 58 | formatPercent = fromOptions 59 | { defaults | 60 | , style = PercentStyle 61 | , maximumSignificantDigits = Just 3 62 | } 63 | |> format 64 | in 65 | formatPercent 0.12345 -- 12.3% 66 | -} 67 | fromOptions : Options -> NumberFormat 68 | fromOptions = 69 | Native.Intl.NumberFormat.fromOptions 70 | 71 | 72 | {-| Format a number according to the rules of the NumberFormat. 73 | 74 | format (fromLocale Locale.en) 456789.123 75 | -} 76 | format : NumberFormat -> number -> String 77 | format = 78 | Native.Intl.NumberFormat.format 79 | 80 | 81 | {-| An Options record, containing the possible settings for a NumberFormat 82 | object. 83 | 84 | The min/max properties fall into two groups: `minimumIntegerDigits`, 85 | `minimumFractionDigits`, and `maximumFractionDigits` in one group, 86 | `minimumSignificantDigits` and `maximumSignificantDigits` in the other. If at 87 | least one property from the second group is not `Nothing`, then the first group 88 | is ignored. 89 | -} 90 | type alias Options = 91 | { locale : Locale 92 | , style : Style 93 | , currency : Currency 94 | , currencyDisplay : CurrencyDisplay 95 | , useGrouping : Bool 96 | , minimumIntegerDigits : Maybe Int 97 | , minimumFractionDigits : Maybe Int 98 | , maximumFractionDigits : Maybe Int 99 | , minimumSignificantDigits : Maybe Int 100 | , maximumSignificantDigits : Maybe Int 101 | } 102 | 103 | 104 | {-| Style of the number format. 105 | -} 106 | type Style 107 | = PercentStyle 108 | | CurrencyStyle 109 | | DecimalStyle 110 | 111 | 112 | {-| How to display the currency information. 113 | -} 114 | type CurrencyDisplay 115 | = CurrencyCode 116 | | CurrencyName 117 | | CurrencySymbol 118 | 119 | 120 | {-| Returns the default options. This is helpful if you only care to change a 121 | few options. 122 | 123 | options : Options 124 | options = 125 | { defaults | style = PercentStyle } 126 | -} 127 | defaults : Options 128 | defaults = 129 | { locale = en 130 | , style = DecimalStyle 131 | , currency = usd 132 | , currencyDisplay = CurrencySymbol 133 | , useGrouping = True 134 | , minimumIntegerDigits = Nothing 135 | , minimumFractionDigits = Nothing 136 | , maximumFractionDigits = Nothing 137 | , minimumSignificantDigits = Nothing 138 | , maximumSignificantDigits = Nothing 139 | } 140 | 141 | 142 | {-| Returns the locale and formatting options computed when the NumberFormat 143 | was created. 144 | 145 | case (resolvedOptions numberFormat).style of 146 | CurrencyStyle -> "Total Money: " 147 | PercentStyle -> "Total Percent: " 148 | DecimalStyle -> "Total Quantity: " 149 | -} 150 | resolvedOptions : NumberFormat -> Options 151 | resolvedOptions = 152 | Native.Intl.NumberFormat.resolvedOptions 153 | 154 | 155 | {-| Returns a list from the provided languages that are supported without having 156 | to fall back to the runtime's default language. 157 | 158 | case fromLanguageTag "tlh" of 159 | Just klingon -> 160 | if isEmpty (supportedLocalesOf [ klingon ]) then 161 | "I can't sort Klingon text" 162 | else 163 | "Make it so, Number One" 164 | Nothing -> 165 | "Khaaaaan!" 166 | -} 167 | supportedLocalesOf : List Locale -> List Locale 168 | supportedLocalesOf = 169 | Native.Intl.NumberFormat.supportedLocalesOf 170 | -------------------------------------------------------------------------------- /src/Intl/PluralRules.elm: -------------------------------------------------------------------------------- 1 | module Intl.PluralRules 2 | exposing 3 | ( PluralRules 4 | , fromLocale 5 | , select 6 | , supportedLocalesOf 7 | ) 8 | 9 | {-| A library that enable plural sensitive formatting and plural language rules. 10 | This module binds to [Intl.PluralRules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/PluralRules). 11 | 12 | # Create 13 | @docs PluralRules 14 | 15 | # Use 16 | @docs select 17 | 18 | # Support and options 19 | 20 | @docs supportedLocalesOf 21 | -} 22 | 23 | import Native.Intl.PluralRules 24 | import Intl.Locale as Locale 25 | 26 | 27 | {-| A PluralRules object, for finding the correct plural form category 28 | for a number 29 | -} 30 | type PluralRules 31 | = PluralRules 32 | 33 | 34 | {-| Creates a PluralRules object that contains the rules for a given locale 35 | -} 36 | fromLocale : Locale.Locale -> PluralRules 37 | fromLocale = 38 | Native.Intl.PluralRules.fromLocale 39 | 40 | 41 | {-| Given a PluralRules object and a number, returns the CLDR plural rules 42 | category for that number 43 | -} 44 | select : PluralRules -> number -> String 45 | select = 46 | Native.Intl.PluralRules.select 47 | 48 | 49 | {-| Returns a list from the provided languages that are supported without having 50 | to fall back to the runtime's default language. 51 | -} 52 | supportedLocalesOf : List Locale.Locale -> List Locale.Locale 53 | supportedLocalesOf = 54 | Native.Intl.PluralRules.supportedLocalesOf 55 | -------------------------------------------------------------------------------- /src/Intl/TimeZone.elm: -------------------------------------------------------------------------------- 1 | module Intl.TimeZone 2 | exposing 3 | ( TimeZone 4 | , fromIANATimeZoneName 5 | , toIANATimeZoneName 6 | , utc 7 | ) 8 | 9 | {-| A TimeZone represents a time zone from the IANA database. 10 | 11 | @docs TimeZone, fromIANATimeZoneName, toIANATimeZoneName 12 | 13 | # Predefined TimeZones 14 | 15 | The UTC time zones has been pre-defined for convenience. For additional time 16 | zones, you will need to use an [IANA time zone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List). 17 | 18 | @docs utc 19 | -} 20 | 21 | import Native.Intl.TimeZone 22 | 23 | 24 | {-| The TimeZone type holds a valid IANA time zone. 25 | -} 26 | type TimeZone 27 | = TimeZone String 28 | 29 | 30 | {-| Checks the string as a valid time zone, and returns a TimeZone if it is. 31 | 32 | -- Hawaii time 33 | fromIANATimeZoneName "Pacific/Honolulu" 34 | 35 | If a string is passed that is not a valid time zone, `Nothing` will be returned. 36 | The string is matched in a case-insensitive manner. 37 | -} 38 | fromIANATimeZoneName : String -> Maybe TimeZone 39 | fromIANATimeZoneName = 40 | Native.Intl.TimeZone.fromIANATimeZoneName 41 | 42 | 43 | {-| Gets the canonicalized string time zone name from a TimeZone. 44 | 45 | toIANATimeZoneName TimeZone.utc == "UTC" 46 | -} 47 | toIANATimeZoneName : TimeZone -> String 48 | toIANATimeZoneName timeZone = 49 | case timeZone of 50 | TimeZone name -> 51 | name 52 | 53 | 54 | {-| Coordinated Universal Time 55 | -} 56 | utc : TimeZone 57 | utc = 58 | TimeZone "UTC" 59 | -------------------------------------------------------------------------------- /src/Native/Intl/Collator.js: -------------------------------------------------------------------------------- 1 | // import Native.Intl.Locale // 2 | // import Intl.Locale // 3 | 4 | /* global 5 | F3 6 | _thetalecrafter$elm_intl$Native_Intl_Locale 7 | _thetalecrafter$elm_intl$Intl_Locale$Locale 8 | _thetalecrafter$elm_intl$Intl_Collator$Search 9 | _thetalecrafter$elm_intl$Intl_Collator$Sort 10 | _thetalecrafter$elm_intl$Intl_Collator$Base 11 | _thetalecrafter$elm_intl$Intl_Collator$Accent 12 | _thetalecrafter$elm_intl$Intl_Collator$Case 13 | _thetalecrafter$elm_intl$Intl_Collator$Variant 14 | _thetalecrafter$elm_intl$Intl_Collator$Upper 15 | _thetalecrafter$elm_intl$Intl_Collator$Lower 16 | _thetalecrafter$elm_intl$Intl_Collator$Default 17 | */ 18 | /* eslint-disable camelcase */ 19 | 20 | // eslint-disable-next-line 21 | var _thetalecrafter$elm_intl$Native_Intl_Collator = function () { 22 | // This will create an early error if Intl is not supported at all, and falls 23 | // back to a shim if Collator is missing from Intl. 24 | var Collator = Intl.Collator || (function () { 25 | function Collator () {} 26 | Collator.prototype = { 27 | compare: function (string1, string2) { 28 | return string1.localeCompare(string2) 29 | }, 30 | resolvedOptions: function () { 31 | return { 32 | locale: 'x-shim', 33 | usage: 'sort', 34 | sensitivity: 'variant', 35 | ignorePunctuation: false, 36 | numeric: false, 37 | caseFirst: 'false' 38 | } 39 | } 40 | } 41 | Collator.supportedLocalesOf = function () { 42 | return [] // doesn't pretend to support any locale 43 | } 44 | return Collator 45 | }()) 46 | 47 | function usageToUnion (value) { 48 | switch (value) { 49 | case 'search': 50 | return _thetalecrafter$elm_intl$Intl_Collator$Search 51 | case 'sort': 52 | default: 53 | return _thetalecrafter$elm_intl$Intl_Collator$Sort 54 | } 55 | } 56 | 57 | function usageFromUnion (value) { 58 | switch (value) { 59 | case _thetalecrafter$elm_intl$Intl_Collator$Search: 60 | return 'search' 61 | case _thetalecrafter$elm_intl$Intl_Collator$Sort: 62 | default: 63 | return 'sort' 64 | } 65 | } 66 | 67 | function sensitivityToUnion (value) { 68 | switch (value) { 69 | case 'base': 70 | return _thetalecrafter$elm_intl$Intl_Collator$Base 71 | case 'accent': 72 | return _thetalecrafter$elm_intl$Intl_Collator$Accent 73 | case 'case': 74 | return _thetalecrafter$elm_intl$Intl_Collator$Case 75 | case 'variant': 76 | default: 77 | return _thetalecrafter$elm_intl$Intl_Collator$Variant 78 | } 79 | } 80 | 81 | function sensitivityFromUnion (value) { 82 | switch (value) { 83 | case _thetalecrafter$elm_intl$Intl_Collator$Base: 84 | return 'base' 85 | case _thetalecrafter$elm_intl$Intl_Collator$Accent: 86 | return 'accent' 87 | case _thetalecrafter$elm_intl$Intl_Collator$Case: 88 | return 'case' 89 | case _thetalecrafter$elm_intl$Intl_Collator$Variant: 90 | default: 91 | return 'variant' 92 | } 93 | } 94 | 95 | function caseFirstToUnion (value) { 96 | switch (value) { 97 | case 'upper': 98 | return _thetalecrafter$elm_intl$Intl_Collator$Upper 99 | case 'lower': 100 | return _thetalecrafter$elm_intl$Intl_Collator$Lower 101 | case 'false': 102 | default: 103 | return _thetalecrafter$elm_intl$Intl_Collator$Default 104 | } 105 | } 106 | 107 | function caseFirstFromUnion (value) { 108 | switch (value) { 109 | case _thetalecrafter$elm_intl$Intl_Collator$Upper: 110 | return 'upper' 111 | case _thetalecrafter$elm_intl$Intl_Collator$Lower: 112 | return 'lower' 113 | case _thetalecrafter$elm_intl$Intl_Collator$Default: 114 | default: 115 | return 'false' 116 | } 117 | } 118 | 119 | function fromLocale (locale) { 120 | return new Collator(locale._0) 121 | } 122 | 123 | function fromOptions (record) { 124 | var locale = record.locale._0 125 | var options = { 126 | usage: usageFromUnion(record.usage), 127 | sensitivity: sensitivityFromUnion(record.sensitivity), 128 | ignorePunctuation: record.ignorePunctuation, 129 | numeric: record.numeric, 130 | caseFirst: caseFirstFromUnion(record.caseFirst) 131 | } 132 | return new Collator(locale, options) 133 | } 134 | 135 | function compare (collator, string1, string2) { 136 | var value = collator.compare(string1, string2) 137 | return { ctor: value === 0 ? 'EQ' : value < 0 ? 'LT' : 'GT' } 138 | } 139 | 140 | function resolvedOptions (collator) { 141 | var options = collator.resolvedOptions() 142 | return { 143 | locale: _thetalecrafter$elm_intl$Intl_Locale$Locale(options.locale), 144 | usage: usageToUnion(options.usage), 145 | sensitivity: sensitivityToUnion(options.sensitivity), 146 | ignorePunctuation: options.ignorePunctuation, 147 | numeric: options.numeric, 148 | caseFirst: caseFirstToUnion(options.caseFirst) 149 | } 150 | } 151 | 152 | function supportedLocalesOf (list) { 153 | return _thetalecrafter$elm_intl$Native_Intl_Locale 154 | .supportedLocalesOf(Collator, list) 155 | } 156 | 157 | return { 158 | fromLocale: fromLocale, 159 | fromOptions: fromOptions, 160 | compare: F3(compare), 161 | resolvedOptions: resolvedOptions, 162 | supportedLocalesOf: supportedLocalesOf 163 | } 164 | }() 165 | -------------------------------------------------------------------------------- /src/Native/Intl/DateTimeFormat.js: -------------------------------------------------------------------------------- 1 | // import Native.Intl.Locale // 2 | // import Intl.Locale // 3 | // import Intl.TimeZone // 4 | 5 | /* global 6 | F2 7 | _thetalecrafter$elm_intl$Native_Intl_Locale 8 | _thetalecrafter$elm_intl$Intl_Locale$Locale 9 | _thetalecrafter$elm_intl$Intl_TimeZone$TimeZone 10 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$NarrowName 11 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortName 12 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongName 13 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitName 14 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$NumericNumber 15 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$TwoDigitNumber 16 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitNumber 17 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$NarrowMonth 18 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortMonth 19 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongMonth 20 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$NumericMonth 21 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$TwoDigitMonth 22 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitMonth 23 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortTimeZone 24 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongTimeZone 25 | _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitTimeZone 26 | _elm_lang$core$Maybe$Just 27 | _elm_lang$core$Maybe$Nothing 28 | */ 29 | /* eslint-disable camelcase */ 30 | 31 | // eslint-disable-next-line 32 | var _thetalecrafter$elm_intl$Native_Intl_DateTimeFormat = function () { 33 | // this will create an early error if Intl is not supported 34 | var DateTimeFormat = Intl.DateTimeFormat 35 | 36 | function styleToName (value) { 37 | switch (value) { 38 | case 'narrow': 39 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$NarrowName 40 | case 'short': 41 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortName 42 | case 'long': 43 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongName 44 | default: 45 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitName 46 | } 47 | } 48 | 49 | function styleFromName (value) { 50 | switch (value) { 51 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$NarrowName: 52 | return 'narrow' 53 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortName: 54 | return 'short' 55 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongName: 56 | return 'long' 57 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitName: 58 | default: 59 | } 60 | } 61 | 62 | function styleToNumber (value) { 63 | switch (value) { 64 | case 'numeric': 65 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$NumericNumber 66 | case '2-digit': 67 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$TwoDigitNumber 68 | default: 69 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitNumber 70 | } 71 | } 72 | 73 | function styleFromNumber (value) { 74 | switch (value) { 75 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$NumericNumber: 76 | return 'numeric' 77 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$TwoDigitNumber: 78 | return '2-digit' 79 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitNumber: 80 | default: 81 | } 82 | } 83 | 84 | function styleToMonth (value) { 85 | switch (value) { 86 | case 'narrow': 87 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$NarrowMonth 88 | case 'short': 89 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortMonth 90 | case 'long': 91 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongMonth 92 | case 'numeric': 93 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$NumericMonth 94 | case '2-digit': 95 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$TwoDigitMonth 96 | default: 97 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitMonth 98 | } 99 | } 100 | 101 | function styleFromMonth (value) { 102 | switch (value) { 103 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$NarrowMonth: 104 | return 'narrow' 105 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortMonth: 106 | return 'short' 107 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongMonth: 108 | return 'long' 109 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$NumericMonth: 110 | return 'numeric' 111 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$TwoDigitMonth: 112 | return '2-digit' 113 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitMonth: 114 | default: 115 | } 116 | } 117 | 118 | function styleToTimeZone (value) { 119 | switch (value) { 120 | case 'short': 121 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortTimeZone 122 | case 'long': 123 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongTimeZone 124 | default: 125 | return _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitTimeZone 126 | } 127 | } 128 | 129 | function styleFromTimeZone (value) { 130 | switch (value) { 131 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$ShortTimeZone: 132 | return 'short' 133 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$LongTimeZone: 134 | return 'long' 135 | case _thetalecrafter$elm_intl$Intl_DateTimeFormat$OmitTimeZone: 136 | default: 137 | } 138 | } 139 | 140 | function fromLocale (locale) { 141 | return new DateTimeFormat(locale._0) 142 | } 143 | 144 | function fromOptions (record) { 145 | var locale = record.locale._0 146 | var options = { 147 | timeZone: record.timeZone._0 && record.timeZone._0._0, 148 | hour12: record.hour12._0, 149 | weekday: styleFromName(record.weekday), 150 | era: styleFromName(record.era), 151 | year: styleFromNumber(record.year), 152 | month: styleFromMonth(record.month), 153 | day: styleFromNumber(record.day), 154 | hour: styleFromNumber(record.hour), 155 | minute: styleFromNumber(record.minute), 156 | second: styleFromNumber(record.second), 157 | timeZoneName: styleFromTimeZone(record.timeZoneName) 158 | } 159 | return new DateTimeFormat(locale, options) 160 | } 161 | 162 | function format (dateTimeFormat, value) { 163 | return dateTimeFormat.format(value) 164 | } 165 | 166 | function resolvedOptions (dateTimeFormat) { 167 | var options = dateTimeFormat.resolvedOptions() 168 | return { 169 | locale: _thetalecrafter$elm_intl$Intl_Locale$Locale(options.locale), 170 | timeZone: _elm_lang$core$Maybe$Just( 171 | _thetalecrafter$elm_intl$Intl_TimeZone$TimeZone(options.timeZone) 172 | ), 173 | hour12: options.hour12 == null 174 | ? _elm_lang$core$Maybe$Nothing 175 | : _elm_lang$core$Maybe$Just(options.hour12), 176 | weekday: styleToName(options.weekday), 177 | era: styleToName(options.era), 178 | year: styleToNumber(options.year), 179 | month: styleToMonth(options.month), 180 | day: styleToNumber(options.day), 181 | hour: styleToNumber(options.hour), 182 | minute: styleToNumber(options.minute), 183 | second: styleToNumber(options.second), 184 | timeZoneName: styleToTimeZone(options.timeZoneName) 185 | } 186 | } 187 | 188 | function supportedLocalesOf (list) { 189 | return _thetalecrafter$elm_intl$Native_Intl_Locale 190 | .supportedLocalesOf(DateTimeFormat, list) 191 | } 192 | 193 | return { 194 | fromLocale: fromLocale, 195 | fromOptions: fromOptions, 196 | format: F2(format), 197 | resolvedOptions: resolvedOptions, 198 | supportedLocalesOf: supportedLocalesOf 199 | } 200 | }() 201 | -------------------------------------------------------------------------------- /src/Native/Intl/Locale.js: -------------------------------------------------------------------------------- 1 | // import Native.List // 2 | // import Maybe // 3 | 4 | /* global 5 | _elm_lang$core$Maybe$Just 6 | _elm_lang$core$Maybe$Nothing 7 | _elm_lang$core$Native_List 8 | _thetalecrafter$elm_intl$Intl_Locale$Locale 9 | */ 10 | /* eslint-disable camelcase */ 11 | 12 | // eslint-disable-next-line 13 | var _thetalecrafter$elm_intl$Native_Intl_Locale = function () { 14 | // this will create an early error if Intl is not supported 15 | var checker = Intl.NumberFormat 16 | 17 | function normalize (tag) { 18 | var parts = tag.toLowerCase().split('-') 19 | var index = 0 20 | var canHaveExtLang = index < parts.length && parts[index].length <= 3 21 | if (canHaveExtLang) { 22 | while (++index < parts.length && parts[index].length === 3); 23 | } 24 | var isScript = index < parts.length && parts[index].length === 4 25 | if (isScript) { 26 | parts[index] = parts[index][0].toUpperCase() + parts[index].slice(1) 27 | ++index 28 | } 29 | var isRegion = index < parts.length && parts[index].length === 2 30 | if (isRegion) { 31 | parts[index] = parts[index].toUpperCase() 32 | } 33 | return parts.join('-') 34 | } 35 | 36 | function fromLanguageTag (tag) { 37 | try { 38 | checker.supportedLocalesOf([ tag ]) // throws if the tag is invalid 39 | // use the normalized tag, (doesn't do canonicalization like ji -> yi) 40 | return _elm_lang$core$Maybe$Just( 41 | _thetalecrafter$elm_intl$Intl_Locale$Locale(normalize(tag)) 42 | ) 43 | } catch (e) { 44 | return _elm_lang$core$Maybe$Nothing 45 | } 46 | } 47 | 48 | function supportedLocalesOf (object, list) { 49 | var array = _elm_lang$core$Native_List.toArray(list).map(function (locale) { 50 | return locale._0 51 | }) 52 | var supported = object.supportedLocalesOf(array).map( 53 | _thetalecrafter$elm_intl$Intl_Locale$Locale 54 | ) 55 | return _elm_lang$core$Native_List.fromArray(supported) 56 | } 57 | 58 | return { 59 | fromLanguageTag: fromLanguageTag, 60 | supportedLocalesOf: supportedLocalesOf 61 | } 62 | }() 63 | -------------------------------------------------------------------------------- /src/Native/Intl/NumberFormat.js: -------------------------------------------------------------------------------- 1 | // import Native.Intl.Locale // 2 | // import Intl.Locale // 3 | // import Intl.Currency // 4 | 5 | /* global 6 | F2 7 | _thetalecrafter$elm_intl$Native_Intl_Locale 8 | _thetalecrafter$elm_intl$Intl_Locale$Locale 9 | _thetalecrafter$elm_intl$Intl_Currency$Currency 10 | _thetalecrafter$elm_intl$Intl_NumberFormat$DecimalStyle 11 | _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyStyle 12 | _thetalecrafter$elm_intl$Intl_NumberFormat$PercentStyle 13 | _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencySymbol 14 | _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyCode 15 | _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyName 16 | _elm_lang$core$Maybe$Just 17 | _elm_lang$core$Maybe$Nothing 18 | */ 19 | /* eslint-disable camelcase */ 20 | 21 | // eslint-disable-next-line 22 | var _thetalecrafter$elm_intl$Native_Intl_NumberFormat = function () { 23 | // this will create an early error if Intl is not supported 24 | var NumberFormat = Intl.NumberFormat 25 | 26 | function styleToUnion (value) { 27 | switch (value) { 28 | case 'percent': 29 | return _thetalecrafter$elm_intl$Intl_NumberFormat$PercentStyle 30 | case 'currency': 31 | return _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyStyle 32 | case 'decimal': 33 | default: 34 | return _thetalecrafter$elm_intl$Intl_NumberFormat$DecimalStyle 35 | } 36 | } 37 | 38 | function styleFromUnion (value) { 39 | switch (value) { 40 | case _thetalecrafter$elm_intl$Intl_NumberFormat$PercentStyle: 41 | return 'percent' 42 | case _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyStyle: 43 | return 'currency' 44 | case _thetalecrafter$elm_intl$Intl_NumberFormat$DecimalStyle: 45 | default: 46 | return 'decimal' 47 | } 48 | } 49 | 50 | function currencyDisplayToUnion (value) { 51 | switch (value) { 52 | case 'code': 53 | return _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyCode 54 | case 'name': 55 | return _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyName 56 | case 'symbol': 57 | default: 58 | return _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencySymbol 59 | } 60 | } 61 | 62 | function currencyDisplayFromUnion (value) { 63 | switch (value) { 64 | case _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyCode: 65 | return 'code' 66 | case _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencyName: 67 | return 'name' 68 | case _thetalecrafter$elm_intl$Intl_NumberFormat$CurrencySymbol: 69 | default: 70 | return 'symbol' 71 | } 72 | } 73 | 74 | function maybeInt (number) { 75 | return number == null 76 | ? _elm_lang$core$Maybe$Nothing 77 | : _elm_lang$core$Maybe$Just(number) 78 | } 79 | 80 | function minMaxInt (min, max, value) { 81 | return value == null 82 | ? value 83 | : Math.min(Math.max(value, min), max) 84 | } 85 | 86 | function fromLocale (locale) { 87 | return new NumberFormat(locale._0) 88 | } 89 | 90 | function fromOptions (record) { 91 | var locale = record.locale._0 92 | var options = { 93 | style: styleFromUnion(record.style), 94 | currency: record.currency._0, 95 | currencyDisplay: currencyDisplayFromUnion(record.currencyDisplay), 96 | useGrouping: record.useGrouping, 97 | // Maybe Number if Just _0 is the number, if Nothing _0 is undefined 98 | minimumIntegerDigits: minMaxInt(1, 21, record.minimumIntegerDigits._0), 99 | minimumFractionDigits: minMaxInt(0, 20, record.minimumFractionDigits._0), 100 | maximumFractionDigits: minMaxInt(0, 20, record.maximumFractionDigits._0), 101 | minimumSignificantDigits: minMaxInt(1, 21, record.minimumSignificantDigits._0), 102 | maximumSignificantDigits: minMaxInt(1, 21, record.maximumSignificantDigits._0) 103 | } 104 | return new NumberFormat(locale, options) 105 | } 106 | 107 | function format (numberFormat, value) { 108 | return numberFormat.format(value) 109 | } 110 | 111 | function resolvedOptions (numberFormat) { 112 | var options = numberFormat.resolvedOptions() 113 | return { 114 | locale: _thetalecrafter$elm_intl$Intl_Locale$Locale(options.locale), 115 | style: styleToUnion(options.style), 116 | currency: _thetalecrafter$elm_intl$Intl_Currency$Currency(options.currency || 'USD'), 117 | currencyDisplay: currencyDisplayToUnion(options.currencyDisplay), 118 | useGrouping: options.useGrouping, 119 | minimumIntegerDigits: maybeInt(options.minimumIntegerDigits), 120 | minimumFractionDigits: maybeInt(options.minimumFractionDigits), 121 | maximumFractionDigits: maybeInt(options.maximumFractionDigits), 122 | minimumSignificantDigits: maybeInt(options.minimumSignificantDigits), 123 | maximumSignificantDigits: maybeInt(options.maximumSignificantDigits) 124 | } 125 | } 126 | 127 | function supportedLocalesOf (list) { 128 | return _thetalecrafter$elm_intl$Native_Intl_Locale 129 | .supportedLocalesOf(NumberFormat, list) 130 | } 131 | 132 | return { 133 | fromLocale: fromLocale, 134 | fromOptions: fromOptions, 135 | format: F2(format), 136 | resolvedOptions: resolvedOptions, 137 | supportedLocalesOf: supportedLocalesOf 138 | } 139 | }() 140 | -------------------------------------------------------------------------------- /src/Native/Intl/PluralRules.js: -------------------------------------------------------------------------------- 1 | // import Native.Intl.Locale // 2 | // import Intl.Locale // 3 | 4 | /* global 5 | F2 6 | _thetalecrafter$elm_intl$Native_Intl_Locale 7 | */ 8 | /* eslint-disable camelcase */ 9 | 10 | // eslint-disable-next-line 11 | var _thetalecrafter$elm_intl$Native_Intl_PluralRules = function () { 12 | var PluralRules = Intl.PluralRules 13 | 14 | // Minimal dummy implementation to avoid crashes 15 | function PluralRulesStub (locales) { 16 | this.select = function (value) { 17 | return 'other' 18 | } 19 | } 20 | PluralRulesStub.supportedLocalesOf = function (list) { 21 | return [] 22 | } 23 | 24 | if (PluralRules === undefined) { 25 | PluralRules = PluralRulesStub 26 | } 27 | 28 | function fromLocale (locale) { 29 | return new PluralRules(locale._0) 30 | } 31 | 32 | function select (pluralRules, value) { 33 | return pluralRules.select(value) 34 | } 35 | 36 | function supportedLocalesOf (list) { 37 | return _thetalecrafter$elm_intl$Native_Intl_Locale 38 | .supportedLocalesOf(PluralRules, list) 39 | } 40 | 41 | return { 42 | fromLocale: fromLocale, 43 | select: F2(select), 44 | supportedLocalesOf: supportedLocalesOf 45 | } 46 | }() 47 | -------------------------------------------------------------------------------- /src/Native/Intl/TimeZone.js: -------------------------------------------------------------------------------- 1 | // import Maybe // 2 | 3 | /* global 4 | _elm_lang$core$Maybe$Just 5 | _elm_lang$core$Maybe$Nothing 6 | _thetalecrafter$elm_intl$Intl_TimeZone$TimeZone 7 | */ 8 | /* eslint-disable camelcase */ 9 | 10 | // eslint-disable-next-line 11 | var _thetalecrafter$elm_intl$Native_Intl_TimeZone = function () { 12 | // this will create an early error if Intl is not supported 13 | var DateTimeFormat = Intl.DateTimeFormat 14 | 15 | function fromIANATimeZoneName (name) { 16 | try { 17 | var tz = DateTimeFormat(undefined, { 18 | timeZone: name 19 | }).resolvedOptions().timeZone 20 | return _elm_lang$core$Maybe$Just( 21 | _thetalecrafter$elm_intl$Intl_TimeZone$TimeZone(tz) 22 | ) 23 | } catch (e) { 24 | return _elm_lang$core$Maybe$Nothing 25 | } 26 | } 27 | 28 | return { 29 | fromIANATimeZoneName: fromIANATimeZoneName 30 | } 31 | }() 32 | -------------------------------------------------------------------------------- /tests/Test/Collator.elm: -------------------------------------------------------------------------------- 1 | module Test.Collator exposing (all) 2 | 3 | import Test exposing (..) 4 | import Expect 5 | import Intl.Collator as Collator 6 | import Intl.Locale as Locale 7 | import List exposing (map, concat, length) 8 | import String exposing (startsWith) 9 | import Maybe exposing (withDefault) 10 | 11 | 12 | all : Test 13 | all = 14 | let 15 | fromLocaleTests = 16 | describe "fromLocale and compare" 17 | (map 18 | (\locale -> 19 | test (Locale.toLanguageTag locale) <| 20 | \() -> 21 | let 22 | compare = 23 | Collator.compare (Collator.fromLocale locale) 24 | in 25 | Expect.equal 26 | [ compare "a" "z", compare "4" "4", compare "c" "a" ] 27 | [ LT, EQ, GT ] 28 | ) 29 | predefinedLocales 30 | ) 31 | 32 | fromOptionsTests = 33 | describe "fromOptions and resolvedOptions" 34 | (map 35 | (\options -> 36 | test ("options " ++ toString options) <| 37 | \() -> 38 | let 39 | resolved = 40 | Collator.resolvedOptions (Collator.fromOptions options) 41 | in 42 | Expect.true "Expected resolved options to match options" 43 | (resolved.usage 44 | == options.usage 45 | && resolved.sensitivity 46 | == options.sensitivity 47 | && resolved.ignorePunctuation 48 | == options.ignorePunctuation 49 | && resolved.numeric 50 | == options.numeric 51 | && resolved.caseFirst 52 | == options.caseFirst 53 | ) 54 | ) 55 | allOptionCombinations 56 | ) 57 | 58 | supportedLocalesOfTests = 59 | describe "supportedLocalesOf" 60 | [ test "at least one predefined locale is supported" <| 61 | \() -> 62 | Expect.atLeast 1 63 | (length (Collator.supportedLocalesOf predefinedLocales)) 64 | , test "not all locales are supported" <| 65 | \() -> 66 | Expect.equal 67 | (Collator.supportedLocalesOf 68 | [ withDefault Locale.en (Locale.fromLanguageTag "tlh") 69 | -- Klingon 70 | , withDefault Locale.en (Locale.fromLanguageTag "qya") 71 | -- Elvish 72 | ] 73 | ) 74 | [] 75 | ] 76 | 77 | defaultsTests = 78 | describe "defaults" 79 | [ test "are valid options" <| 80 | \() -> 81 | Expect.equal 82 | (Collator.resolvedOptions (Collator.fromOptions Collator.defaults)) 83 | Collator.defaults 84 | ] 85 | in 86 | describe "Intl.Collator" 87 | [ fromLocaleTests 88 | , fromOptionsTests 89 | , supportedLocalesOfTests 90 | , defaultsTests 91 | ] 92 | 93 | 94 | predefinedLocales : List Locale.Locale 95 | predefinedLocales = 96 | [ Locale.en 97 | , Locale.zhCN 98 | , Locale.zhTW 99 | , Locale.fr 100 | , Locale.de 101 | , Locale.it 102 | , Locale.ja 103 | , Locale.ko 104 | ] 105 | 106 | 107 | usages : List Collator.Usage 108 | usages = 109 | [ Collator.Sort 110 | , Collator.Search 111 | ] 112 | 113 | 114 | sensitivities : List Collator.Sensitivity 115 | sensitivities = 116 | [ Collator.Base 117 | , Collator.Accent 118 | , Collator.Case 119 | , Collator.Variant 120 | ] 121 | 122 | 123 | caseFirsts : List Collator.CaseFirst 124 | caseFirsts = 125 | [ Collator.Upper 126 | , Collator.Lower 127 | , Collator.Default 128 | ] 129 | 130 | 131 | bools = 132 | [ True 133 | , False 134 | ] 135 | 136 | 137 | allOptionCombinations : List Collator.Options 138 | allOptionCombinations = 139 | predefinedLocales 140 | |> map 141 | (\locale -> 142 | usages 143 | |> map 144 | (\usage -> 145 | sensitivities 146 | |> map 147 | (\sensitivity -> 148 | bools 149 | |> map 150 | (\ignorePunctuation -> 151 | bools 152 | |> map 153 | (\numeric -> 154 | caseFirsts 155 | |> map 156 | (\caseFirst -> 157 | { locale = locale 158 | , usage = usage 159 | , sensitivity = sensitivity 160 | , ignorePunctuation = ignorePunctuation 161 | , numeric = numeric 162 | , caseFirst = caseFirst 163 | } 164 | ) 165 | ) 166 | |> List.concat 167 | ) 168 | |> List.concat 169 | ) 170 | |> List.concat 171 | ) 172 | |> List.concat 173 | ) 174 | |> List.concat 175 | -------------------------------------------------------------------------------- /tests/Test/Currency.elm: -------------------------------------------------------------------------------- 1 | module Test.Currency exposing (all) 2 | 3 | import Test exposing (..) 4 | import Fuzz exposing (string) 5 | import Expect 6 | import Intl.Currency as Currency 7 | import Maybe exposing (..) 8 | import String exposing (toUpper) 9 | 10 | 11 | all : Test 12 | all = 13 | let 14 | fromCurrencyCodeTests = 15 | describe "fromCurrencyCode" 16 | [ test "empty string" <| 17 | \() -> 18 | Expect.equal (Currency.fromCurrencyCode "") Nothing 19 | , test "invalid characters" <| 20 | \() -> 21 | Expect.equal (Currency.fromCurrencyCode "$@#") Nothing 22 | , test "usd" <| 23 | \() -> 24 | Expect.equal (Currency.fromCurrencyCode "usd") (Just Currency.usd) 25 | , test "EUR" <| 26 | \() -> 27 | Expect.equal (Currency.fromCurrencyCode "EUR") (Just Currency.eur) 28 | , test "jpY" <| 29 | \() -> 30 | Expect.equal (Currency.fromCurrencyCode "jpY") (Just Currency.jpy) 31 | , test "gBp" <| 32 | \() -> 33 | Expect.equal (Currency.fromCurrencyCode "gBp") (Just Currency.gbp) 34 | , test "CHf" <| 35 | \() -> 36 | Expect.equal (Currency.fromCurrencyCode "CHf") (Just Currency.chf) 37 | , test "cAD" <| 38 | \() -> 39 | Expect.equal (Currency.fromCurrencyCode "cAD") (Just Currency.cad) 40 | , test "normalizes capitalization" <| 41 | \() -> 42 | Expect.equal 43 | (Currency.fromCurrencyCode "xau" 44 | |> withDefault Currency.cad 45 | |> Currency.toCurrencyCode 46 | ) 47 | "XAU" 48 | , fuzz string "doesn't crash on bad inputs" <| 49 | \code -> 50 | case Currency.fromCurrencyCode code of 51 | Nothing -> 52 | Expect.pass 53 | 54 | Just _ -> 55 | Expect.pass 56 | ] 57 | 58 | toCurrencyCodeTests = 59 | describe "toCurrencyCode" 60 | [ test "usd" <| 61 | \() -> 62 | Expect.equal (Currency.toCurrencyCode Currency.usd) "USD" 63 | , test "eur" <| 64 | \() -> 65 | Expect.equal (Currency.toCurrencyCode Currency.eur) "EUR" 66 | , test "jpy" <| 67 | \() -> 68 | Expect.equal (Currency.toCurrencyCode Currency.jpy) "JPY" 69 | , test "gbp" <| 70 | \() -> 71 | Expect.equal (Currency.toCurrencyCode Currency.gbp) "GBP" 72 | , test "chf" <| 73 | \() -> 74 | Expect.equal (Currency.toCurrencyCode Currency.chf) "CHF" 75 | , test "cad" <| 76 | \() -> 77 | Expect.equal (Currency.toCurrencyCode Currency.cad) "CAD" 78 | , fuzz string "doesn't crash on random Currencies" <| 79 | \code -> 80 | case Currency.fromCurrencyCode code of 81 | Nothing -> 82 | Expect.pass 83 | 84 | Just currency -> 85 | Expect.equal 86 | (Currency.toCurrencyCode currency) 87 | (toUpper code) 88 | ] 89 | in 90 | describe "Intl.Currency" 91 | [ fromCurrencyCodeTests 92 | , toCurrencyCodeTests 93 | ] 94 | -------------------------------------------------------------------------------- /tests/Test/DateTimeFormat.elm: -------------------------------------------------------------------------------- 1 | module Test.DateTimeFormat exposing (all) 2 | 3 | import Test exposing (..) 4 | import Expect 5 | import Intl.DateTimeFormat as DateTimeFormat 6 | import Intl.Locale as Locale 7 | import Intl.TimeZone exposing (utc) 8 | import List exposing (map, concat, length) 9 | import String exposing (startsWith) 10 | import Maybe exposing (Maybe(Just), withDefault) 11 | import Date 12 | import Time exposing (second) 13 | 14 | 15 | all : Test 16 | all = 17 | let 18 | fromLocaleTests = 19 | describe "fromLocale and format" 20 | [ test "English" <| 21 | \() -> 22 | let 23 | format = 24 | DateTimeFormat.format (DateTimeFormat.fromLocale Locale.en) 25 | in 26 | Expect.equal 27 | (format (Date.fromTime (1482683025 * second))) 28 | "12/25/2016" 29 | ] 30 | 31 | formatTests = 32 | describe "format" 33 | (let 34 | defaults = 35 | DateTimeFormat.defaults 36 | 37 | christmas = 38 | -- 2016/12/25, 16:23:45 UTC 39 | (Date.fromTime (1482683025 * second)) 40 | 41 | format dt opts = 42 | DateTimeFormat.format (DateTimeFormat.fromOptions opts) dt 43 | in 44 | [ test "timeZone" <| 45 | \() -> 46 | Expect.equal 47 | (format christmas 48 | { defaults 49 | | timeZone = Just utc 50 | , timeZoneName = DateTimeFormat.ShortTimeZone 51 | , weekday = DateTimeFormat.LongName 52 | } 53 | ) 54 | "Sunday, UTC" 55 | , test "hour12" <| 56 | \() -> 57 | Expect.equal 58 | (format christmas 59 | { defaults 60 | | hour12 = Just True 61 | , hour = DateTimeFormat.NumericNumber 62 | , timeZone = Just utc 63 | } 64 | ) 65 | "4 PM" 66 | , test "weekday" <| 67 | \() -> 68 | Expect.equal 69 | (format christmas { defaults | weekday = DateTimeFormat.LongName }) 70 | "Sunday" 71 | , test "era" <| 72 | \() -> 73 | Expect.equal 74 | (format christmas 75 | { defaults 76 | | era = DateTimeFormat.ShortName 77 | , year = DateTimeFormat.NumericNumber 78 | } 79 | ) 80 | "2016 AD" 81 | , test "year" <| 82 | \() -> 83 | Expect.equal 84 | (format christmas { defaults | year = DateTimeFormat.NumericNumber }) 85 | "2016" 86 | , test "month" <| 87 | \() -> 88 | Expect.equal 89 | (format christmas { defaults | month = DateTimeFormat.LongMonth }) 90 | "December" 91 | , test "day" <| 92 | \() -> 93 | Expect.equal 94 | (format christmas { defaults | day = DateTimeFormat.NumericNumber }) 95 | "25" 96 | , test "hour" <| 97 | \() -> 98 | Expect.equal 99 | (format christmas 100 | { defaults 101 | | hour = DateTimeFormat.NumericNumber 102 | , hour12 = Just False 103 | , timeZone = Just utc 104 | } 105 | ) 106 | "16" 107 | , test "minute" <| 108 | \() -> 109 | Expect.equal 110 | (format christmas { defaults | minute = DateTimeFormat.NumericNumber }) 111 | "23" 112 | , test "second" <| 113 | \() -> 114 | Expect.equal 115 | (format christmas { defaults | second = DateTimeFormat.NumericNumber }) 116 | "45" 117 | , test "timeZoneName" <| 118 | \() -> 119 | Expect.equal 120 | (format christmas 121 | { defaults 122 | | timeZoneName = DateTimeFormat.LongTimeZone 123 | , timeZone = Just utc 124 | , year = DateTimeFormat.NumericNumber 125 | } 126 | ) 127 | "2016, Coordinated Universal Time" 128 | ] 129 | ) 130 | 131 | fromOptionsTests = 132 | describe "fromOptions and resolvedOptions" 133 | (map 134 | (\options -> 135 | test ("options " ++ toString options) <| 136 | \() -> 137 | let 138 | resolved = 139 | DateTimeFormat.resolvedOptions (DateTimeFormat.fromOptions options) 140 | in 141 | Expect.true "Expected resolved options to not have something match" 142 | (resolved.weekday 143 | == options.weekday 144 | || resolved.era 145 | == options.era 146 | || resolved.year 147 | == options.year 148 | || resolved.month 149 | == options.month 150 | || resolved.day 151 | == options.day 152 | || resolved.hour 153 | == options.hour 154 | || resolved.minute 155 | == options.minute 156 | || resolved.second 157 | == options.second 158 | || resolved.timeZoneName 159 | == options.timeZoneName 160 | ) 161 | ) 162 | optionCombinations 163 | ) 164 | 165 | supportedLocalesOfTests = 166 | describe "supportedLocalesOf" 167 | [ test "at least one predefined locale is supported" <| 168 | \() -> 169 | Expect.atLeast 1 170 | (length (DateTimeFormat.supportedLocalesOf predefinedLocales)) 171 | , test "not all locales are supported" <| 172 | \() -> 173 | Expect.equal 174 | (DateTimeFormat.supportedLocalesOf 175 | [ withDefault Locale.en (Locale.fromLanguageTag "tlh") 176 | -- Klingon 177 | , withDefault Locale.en (Locale.fromLanguageTag "qya") 178 | -- Elvish 179 | ] 180 | ) 181 | [] 182 | ] 183 | 184 | defaultsTests = 185 | describe "defaults" 186 | [ test "end up being m/d/y" <| 187 | \() -> 188 | let 189 | resolved = 190 | DateTimeFormat.fromOptions defaults 191 | |> DateTimeFormat.resolvedOptions 192 | in 193 | Expect.true "mdy" 194 | (resolved.year 195 | == DateTimeFormat.NumericNumber 196 | && resolved.month 197 | == DateTimeFormat.NumericMonth 198 | && resolved.day 199 | == DateTimeFormat.NumericNumber 200 | ) 201 | ] 202 | in 203 | describe "Intl.DateTimeFormat" 204 | [ fromLocaleTests 205 | , formatTests 206 | , fromOptionsTests 207 | , supportedLocalesOfTests 208 | , defaultsTests 209 | ] 210 | 211 | 212 | defaults : DateTimeFormat.Options 213 | defaults = 214 | DateTimeFormat.defaults 215 | 216 | 217 | predefinedLocales : List Locale.Locale 218 | predefinedLocales = 219 | [ Locale.en 220 | , Locale.zhCN 221 | , Locale.zhTW 222 | , Locale.fr 223 | , Locale.de 224 | , Locale.it 225 | , Locale.ja 226 | , Locale.ko 227 | ] 228 | 229 | 230 | 231 | {- A couple commons configurations -} 232 | 233 | 234 | optionCombinations : List DateTimeFormat.Options 235 | optionCombinations = 236 | [ { defaults 237 | | weekday = DateTimeFormat.LongName 238 | , year = DateTimeFormat.NumericNumber 239 | , month = DateTimeFormat.LongMonth 240 | , day = DateTimeFormat.NumericNumber 241 | , hour = DateTimeFormat.NumericNumber 242 | , minute = DateTimeFormat.TwoDigitNumber 243 | , second = DateTimeFormat.TwoDigitNumber 244 | } 245 | , { defaults 246 | | weekday = DateTimeFormat.NarrowName 247 | , year = DateTimeFormat.NumericNumber 248 | , month = DateTimeFormat.NarrowMonth 249 | , day = DateTimeFormat.NumericNumber 250 | } 251 | , { defaults 252 | | year = DateTimeFormat.NumericNumber 253 | , month = DateTimeFormat.ShortMonth 254 | , day = DateTimeFormat.NumericNumber 255 | } 256 | , { defaults 257 | | year = DateTimeFormat.NumericNumber 258 | , month = DateTimeFormat.LongMonth 259 | } 260 | , { defaults 261 | | month = DateTimeFormat.LongMonth 262 | , day = DateTimeFormat.NumericNumber 263 | } 264 | , { defaults 265 | | hour = DateTimeFormat.NumericNumber 266 | , minute = DateTimeFormat.TwoDigitNumber 267 | , second = DateTimeFormat.TwoDigitNumber 268 | } 269 | , { defaults 270 | | hour12 = Just False 271 | , hour = DateTimeFormat.TwoDigitNumber 272 | , minute = DateTimeFormat.TwoDigitNumber 273 | } 274 | ] 275 | -------------------------------------------------------------------------------- /tests/Test/Locale.elm: -------------------------------------------------------------------------------- 1 | module Test.Locale exposing (all) 2 | 3 | import Test exposing (..) 4 | import Fuzz exposing (string) 5 | import Expect 6 | import Intl.Locale as Locale 7 | import Maybe exposing (..) 8 | import String exposing (toLower) 9 | 10 | 11 | all : Test 12 | all = 13 | let 14 | fromLanguageTagTests = 15 | describe "fromLanguageTag" 16 | [ test "empty string" <| 17 | \() -> 18 | Expect.equal (Locale.fromLanguageTag "") Nothing 19 | , test "invalid characters" <| 20 | \() -> 21 | Expect.equal (Locale.fromLanguageTag "$@#") Nothing 22 | , test "en" <| 23 | \() -> 24 | Expect.equal (Locale.fromLanguageTag "en") (Just Locale.en) 25 | , test "zh-CN" <| 26 | \() -> 27 | Expect.equal (Locale.fromLanguageTag "zh-CN") (Just Locale.zhCN) 28 | , test "zh-tw" <| 29 | \() -> 30 | Expect.equal (Locale.fromLanguageTag "zh-tw") (Just Locale.zhTW) 31 | , test "fr" <| 32 | \() -> 33 | Expect.equal (Locale.fromLanguageTag "fr") (Just Locale.fr) 34 | , test "De" <| 35 | \() -> 36 | Expect.equal (Locale.fromLanguageTag "De") (Just Locale.de) 37 | , test "iT" <| 38 | \() -> 39 | Expect.equal (Locale.fromLanguageTag "iT") (Just Locale.it) 40 | , test "ja" <| 41 | \() -> 42 | Expect.equal (Locale.fromLanguageTag "ja") (Just Locale.ja) 43 | , test "ko" <| 44 | \() -> 45 | Expect.equal (Locale.fromLanguageTag "ko") (Just Locale.ko) 46 | , test "en-US" <| 47 | \() -> 48 | Expect.notEqual (Locale.fromLanguageTag "en-US") (Just Locale.en) 49 | , test "normalizes capitalization" <| 50 | \() -> 51 | Expect.equal 52 | (Locale.fromLanguageTag "ZH-hant-hk" 53 | |> withDefault Locale.en 54 | |> Locale.toLanguageTag 55 | ) 56 | "zh-Hant-HK" 57 | , fuzz string "doesn't crash on bad inputs" <| 58 | \tag -> 59 | case Locale.fromLanguageTag tag of 60 | Nothing -> 61 | Expect.pass 62 | 63 | Just _ -> 64 | Expect.pass 65 | ] 66 | 67 | toLanguageTagTests = 68 | describe "toLanguageTag" 69 | [ test "en" <| 70 | \() -> 71 | Expect.equal (Locale.toLanguageTag Locale.en) "en" 72 | , test "zh-CN" <| 73 | \() -> 74 | Expect.equal (Locale.toLanguageTag Locale.zhCN) "zh-CN" 75 | , test "zh-TW" <| 76 | \() -> 77 | Expect.equal (Locale.toLanguageTag Locale.zhTW) "zh-TW" 78 | , test "fr" <| 79 | \() -> 80 | Expect.equal (Locale.toLanguageTag Locale.fr) "fr" 81 | , test "de" <| 82 | \() -> 83 | Expect.equal (Locale.toLanguageTag Locale.de) "de" 84 | , test "it" <| 85 | \() -> 86 | Expect.equal (Locale.toLanguageTag Locale.it) "it" 87 | , test "ja" <| 88 | \() -> 89 | Expect.equal (Locale.toLanguageTag Locale.ja) "ja" 90 | , test "ko" <| 91 | \() -> 92 | Expect.equal (Locale.toLanguageTag Locale.ko) "ko" 93 | , fuzz string "doesn't crash on random locales" <| 94 | \tag -> 95 | case Locale.fromLanguageTag tag of 96 | Nothing -> 97 | Expect.pass 98 | 99 | Just locale -> 100 | Expect.equal 101 | (toLower (Locale.toLanguageTag locale)) 102 | (toLower tag) 103 | ] 104 | in 105 | describe "Intl.Locale" 106 | [ fromLanguageTagTests 107 | , toLanguageTagTests 108 | ] 109 | -------------------------------------------------------------------------------- /tests/Test/NumberFormat.elm: -------------------------------------------------------------------------------- 1 | module Test.NumberFormat exposing (all) 2 | 3 | import Test exposing (..) 4 | import Expect 5 | import Intl.NumberFormat as NumberFormat 6 | import Intl.Locale as Locale 7 | import Intl.Currency as Currency 8 | import List exposing (map, concat, length) 9 | import Maybe exposing (Maybe(Just), withDefault) 10 | 11 | 12 | all : Test 13 | all = 14 | let 15 | fromLocaleTests = 16 | describe "fromLocale" 17 | [ test "formats numbers" <| 18 | \() -> 19 | let 20 | format = 21 | NumberFormat.format (NumberFormat.fromLocale Locale.en) 22 | in 23 | Expect.equal (format 12345.6789) "12,345.679" 24 | ] 25 | 26 | fromOptionsTests = 27 | describe "specified options" 28 | [ let 29 | options = 30 | { defaults 31 | | style = NumberFormat.PercentStyle 32 | , maximumSignificantDigits = Just 3 33 | } 34 | 35 | format = 36 | NumberFormat.format (NumberFormat.fromOptions options) 37 | 38 | resolved = 39 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 40 | in 41 | describe "percent maximumSignificantDigits" 42 | [ test "format" <| 43 | \() -> Expect.equal (format 0.12345) "12.3%" 44 | , test "resolvedOptions" <| 45 | \() -> 46 | Expect.equal 47 | resolved.maximumSignificantDigits 48 | options.maximumSignificantDigits 49 | ] 50 | , let 51 | options = 52 | { defaults 53 | | style = NumberFormat.PercentStyle 54 | , minimumFractionDigits = Just 2 55 | } 56 | 57 | format = 58 | NumberFormat.format (NumberFormat.fromOptions options) 59 | 60 | resolved = 61 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 62 | in 63 | describe "percent minimumFractionDigits" 64 | [ test "format" <| 65 | \() -> Expect.equal (format 1) "100.00%" 66 | , test "resolvedOptions" <| 67 | \() -> 68 | Expect.equal 69 | resolved.minimumFractionDigits 70 | options.minimumFractionDigits 71 | ] 72 | , let 73 | options = 74 | { defaults 75 | | style = NumberFormat.CurrencyStyle 76 | , currency = Currency.cad 77 | , currencyDisplay = NumberFormat.CurrencyCode 78 | , minimumFractionDigits = Just 2 79 | } 80 | 81 | format = 82 | NumberFormat.format (NumberFormat.fromOptions options) 83 | 84 | resolved = 85 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 86 | in 87 | describe "dollars" 88 | [ test "format" <| 89 | \() -> Expect.equal (format 42) "CAD42.00" 90 | , test "resolvedOptions" <| 91 | \() -> 92 | Expect.equal 93 | resolved.currency 94 | options.currency 95 | ] 96 | , let 97 | options = 98 | { defaults 99 | | style = NumberFormat.CurrencyStyle 100 | , currency = Currency.chf 101 | , currencyDisplay = NumberFormat.CurrencyName 102 | } 103 | 104 | format = 105 | NumberFormat.format (NumberFormat.fromOptions options) 106 | 107 | resolved = 108 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 109 | in 110 | describe "francs" 111 | [ test "format" <| 112 | \() -> Expect.equal (format 42) "42.00 Swiss francs" 113 | , test "resolvedOptions" <| 114 | \() -> 115 | Expect.equal 116 | resolved.currencyDisplay 117 | options.currencyDisplay 118 | ] 119 | , let 120 | options = 121 | { defaults 122 | | style = NumberFormat.CurrencyStyle 123 | , currency = Currency.jpy 124 | , currencyDisplay = NumberFormat.CurrencySymbol 125 | } 126 | 127 | format = 128 | NumberFormat.format (NumberFormat.fromOptions options) 129 | 130 | resolved = 131 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 132 | in 133 | describe "yen" 134 | [ test "format" <| 135 | \() -> Expect.equal (format 3456) "¥3,456" 136 | , test "resolvedOptions" <| 137 | \() -> 138 | Expect.equal 139 | resolved.currencyDisplay 140 | options.currencyDisplay 141 | ] 142 | , let 143 | options = 144 | { defaults 145 | | minimumIntegerDigits = Just 2 146 | } 147 | 148 | format = 149 | NumberFormat.format (NumberFormat.fromOptions options) 150 | 151 | resolved = 152 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 153 | in 154 | describe "minimumIntegerDigits" 155 | [ test "format" <| 156 | \() -> Expect.equal (format 0.3456) "00.346" 157 | , test "resolvedOptions" <| 158 | \() -> 159 | Expect.equal 160 | resolved.minimumIntegerDigits 161 | options.minimumIntegerDigits 162 | ] 163 | , let 164 | options = 165 | { defaults 166 | | minimumFractionDigits = Just 3 167 | } 168 | 169 | format = 170 | NumberFormat.format (NumberFormat.fromOptions options) 171 | 172 | resolved = 173 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 174 | in 175 | describe "minimumFractionDigits" 176 | [ test "format" <| 177 | \() -> Expect.equal (format 2) "2.000" 178 | , test "resolvedOptions" <| 179 | \() -> 180 | Expect.equal 181 | resolved.minimumFractionDigits 182 | options.minimumFractionDigits 183 | ] 184 | , let 185 | options = 186 | { defaults 187 | | maximumFractionDigits = Just 1 188 | } 189 | 190 | format = 191 | NumberFormat.format (NumberFormat.fromOptions options) 192 | 193 | resolved = 194 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 195 | in 196 | describe "maximumFractionDigits" 197 | [ test "format" <| 198 | \() -> Expect.equal (format 0.1234) "0.1" 199 | , test "resolvedOptions" <| 200 | \() -> 201 | Expect.equal 202 | resolved.maximumFractionDigits 203 | options.maximumFractionDigits 204 | ] 205 | , let 206 | options = 207 | { defaults 208 | | minimumSignificantDigits = Just 5 209 | } 210 | 211 | format = 212 | NumberFormat.format (NumberFormat.fromOptions options) 213 | 214 | resolved = 215 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 216 | in 217 | describe "minimumSignificantDigits" 218 | [ test "format" <| 219 | \() -> Expect.equal (format 0) "0.0000" 220 | , test "resolvedOptions" <| 221 | \() -> 222 | Expect.equal 223 | resolved.minimumSignificantDigits 224 | options.minimumSignificantDigits 225 | ] 226 | , let 227 | options = 228 | { defaults 229 | | maximumSignificantDigits = Just 1 230 | } 231 | 232 | format = 233 | NumberFormat.format (NumberFormat.fromOptions options) 234 | 235 | resolved = 236 | NumberFormat.resolvedOptions (NumberFormat.fromOptions options) 237 | in 238 | describe "maximumSignificantDigits" 239 | [ test "format" <| 240 | \() -> Expect.equal (format 123.456) "100" 241 | , test "resolvedOptions" <| 242 | \() -> 243 | Expect.equal 244 | resolved.maximumSignificantDigits 245 | options.maximumSignificantDigits 246 | ] 247 | ] 248 | 249 | supportedLocalesOfTests = 250 | describe "supportedLocalesOf" 251 | [ test "at least one predefined locale is supported" <| 252 | \() -> 253 | Expect.atLeast 1 254 | (length (NumberFormat.supportedLocalesOf predefinedLocales)) 255 | , test "not all locales are supported" <| 256 | \() -> 257 | Expect.equal 258 | (NumberFormat.supportedLocalesOf 259 | [ withDefault Locale.en (Locale.fromLanguageTag "tlh") 260 | -- Klingon 261 | , withDefault Locale.en (Locale.fromLanguageTag "qya") 262 | -- Elvish 263 | ] 264 | ) 265 | [] 266 | ] 267 | 268 | defaultsTests = 269 | describe "defaults" 270 | [ test "ends up having specific options" <| 271 | \() -> 272 | let 273 | resolved = 274 | NumberFormat.fromOptions defaults 275 | |> NumberFormat.resolvedOptions 276 | in 277 | Expect.equal resolved 278 | { defaults 279 | | maximumFractionDigits = Just 3 280 | , minimumFractionDigits = Just 0 281 | , minimumIntegerDigits = Just 1 282 | } 283 | ] 284 | in 285 | describe "Intl.NumberFormat" 286 | [ fromLocaleTests 287 | , fromOptionsTests 288 | , supportedLocalesOfTests 289 | , defaultsTests 290 | ] 291 | 292 | 293 | defaults : NumberFormat.Options 294 | defaults = 295 | NumberFormat.defaults 296 | 297 | 298 | predefinedLocales : List Locale.Locale 299 | predefinedLocales = 300 | [ Locale.en 301 | , Locale.zhCN 302 | , Locale.zhTW 303 | , Locale.fr 304 | , Locale.de 305 | , Locale.it 306 | , Locale.ja 307 | , Locale.ko 308 | ] 309 | -------------------------------------------------------------------------------- /tests/Test/PluralRules.elm: -------------------------------------------------------------------------------- 1 | module Test.PluralRules exposing (all) 2 | 3 | import Test exposing (..) 4 | import Expect 5 | import Intl.PluralRules as PluralRules 6 | import Intl.Locale as Locale 7 | import Intl.Currency as Currency 8 | import List exposing (map, concat, length) 9 | import Maybe exposing (Maybe(Just), withDefault) 10 | 11 | 12 | all : Test 13 | all = 14 | let 15 | select = 16 | PluralRules.select (PluralRules.fromLocale Locale.en) 17 | 18 | fromLocaleTests = 19 | describe "fromLocale" 20 | [ test "selects plural form for 1" <| 21 | \() -> 22 | Expect.equal (select 1) "one" 23 | , test "selects plural form for 1.1" <| 24 | \() -> 25 | Expect.equal (select 1.1) "other" 26 | , test "selects plural form for 2" <| 27 | \() -> 28 | Expect.equal (select 2) "other" 29 | ] 30 | 31 | supportedLocalesOfTests = 32 | describe "supportedLocalesOf" 33 | [ test "at least one predefined locale is supported" <| 34 | \() -> 35 | Expect.atLeast 1 36 | (length (PluralRules.supportedLocalesOf predefinedLocales)) 37 | , test "not all locales are supported" <| 38 | \() -> 39 | Expect.equal 40 | (PluralRules.supportedLocalesOf 41 | [ withDefault Locale.en (Locale.fromLanguageTag "tlh") 42 | -- Klingon 43 | , withDefault Locale.en (Locale.fromLanguageTag "qya") 44 | -- Elvish 45 | ] 46 | ) 47 | [] 48 | ] 49 | in 50 | describe "Intl.PluralRules" 51 | [ fromLocaleTests 52 | , supportedLocalesOfTests 53 | ] 54 | 55 | 56 | predefinedLocales : List Locale.Locale 57 | predefinedLocales = 58 | [ Locale.en 59 | , Locale.zhCN 60 | ] 61 | -------------------------------------------------------------------------------- /tests/Test/TimeZone.elm: -------------------------------------------------------------------------------- 1 | module Test.TimeZone exposing (all) 2 | 3 | import Test exposing (..) 4 | import Fuzz exposing (string) 5 | import Expect 6 | import Intl.TimeZone as TimeZone 7 | import Maybe exposing (..) 8 | import String exposing (toLower) 9 | 10 | 11 | all : Test 12 | all = 13 | let 14 | fromIANATimeZoneNameTests = 15 | describe "fromIANATimeZoneName" 16 | [ test "empty string" <| 17 | \() -> 18 | Expect.equal (TimeZone.fromIANATimeZoneName "") Nothing 19 | , test "invalid characters" <| 20 | \() -> 21 | Expect.equal (TimeZone.fromIANATimeZoneName "$@#") Nothing 22 | , test "utc" <| 23 | \() -> 24 | Expect.equal (TimeZone.fromIANATimeZoneName "utc") (Just TimeZone.utc) 25 | , test "maps Etc/UTC to UTC" <| 26 | \() -> 27 | Expect.equal (TimeZone.fromIANATimeZoneName "Etc/UTC") (Just TimeZone.utc) 28 | , test "canonicalizes time zones" <| 29 | \() -> 30 | Expect.equal 31 | (TimeZone.fromIANATimeZoneName "Europe/Belfast" 32 | |> withDefault TimeZone.utc 33 | |> TimeZone.toIANATimeZoneName 34 | ) 35 | "Europe/London" 36 | , test "normalizes capitalization" <| 37 | \() -> 38 | Expect.equal 39 | (TimeZone.fromIANATimeZoneName "aMerIcA/BOISE" 40 | |> withDefault TimeZone.utc 41 | |> TimeZone.toIANATimeZoneName 42 | ) 43 | "America/Boise" 44 | , fuzz string "doesn't crash on bad inputs" <| 45 | \name -> 46 | case TimeZone.fromIANATimeZoneName name of 47 | Nothing -> 48 | Expect.pass 49 | 50 | Just _ -> 51 | Expect.pass 52 | ] 53 | 54 | toIANATimeZoneNameTests = 55 | describe "toIANATimeZoneName" 56 | [ test "utc" <| 57 | \() -> 58 | Expect.equal (TimeZone.toIANATimeZoneName TimeZone.utc) "UTC" 59 | , fuzz string "doesn't crash on random TimeZones" <| 60 | \name -> 61 | case TimeZone.fromIANATimeZoneName name of 62 | Nothing -> 63 | Expect.pass 64 | 65 | Just timeZone -> 66 | Expect.equal 67 | (toLower (TimeZone.toIANATimeZoneName timeZone)) 68 | (toLower name) 69 | ] 70 | in 71 | describe "Intl.TimeZone" 72 | [ fromIANATimeZoneNameTests 73 | , toIANATimeZoneNameTests 74 | ] 75 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Tests for bindings to Internationalization APIs", 4 | "repository": "https://github.com/thetalecrafter/elm-intl.git", 5 | "license": "BSD-3-Clause", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "native-modules": true, 12 | "dependencies": { 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 14 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0" 15 | }, 16 | "elm-version": "0.18.0 <= v < 0.19.0" 17 | } 18 | --------------------------------------------------------------------------------