fluent-icu 1.0.0 → 1.1.0
raw patch · 10 files changed
+87/−66 lines, 10 filesdep ~fluentsetup-changedPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: fluent
API changes (from Hackage documentation)
+ Language.Fluent.ICU: Bundle :: NonEmpty locale -> [Resource] -> HashMap Identifier ([Pattern] -> HashMap Identifier Value -> Either String Pattern) -> Bool -> Bundle locale
+ Language.Fluent.ICU: Cardinal :: Form
+ Language.Fluent.ICU: Code :: CurrencyDisplay
+ Language.Fluent.ICU: Currency :: Text -> Style
+ Language.Fluent.ICU: Decimal :: Style
+ Language.Fluent.ICU: Few :: Category
+ Language.Fluent.ICU: Isolated :: Pattern -> PatternElement
+ Language.Fluent.ICU: Long :: Width
+ Language.Fluent.ICU: Many :: Category
+ Language.Fluent.ICU: Name :: CurrencyDisplay
+ Language.Fluent.ICU: Narrow :: Width
+ Language.Fluent.ICU: NumberOptions :: Form -> Style -> CurrencyDisplay -> Width -> Bool -> Int -> Int -> Maybe Int -> Maybe Int -> Maybe Int -> NumberOptions
+ Language.Fluent.ICU: Numeric :: Digits
+ Language.Fluent.ICU: One :: Category
+ Language.Fluent.ICU: Ordinal :: Form
+ Language.Fluent.ICU: Other :: Category
+ Language.Fluent.ICU: Pattern :: NonEmpty PatternElement -> Pattern
+ Language.Fluent.ICU: Percent :: Style
+ Language.Fluent.ICU: Reference :: Either String Identifier -> Maybe AttributeAccessor -> HashMap Text Value -> [Override] -> Reference
+ Language.Fluent.ICU: Short :: Width
+ Language.Fluent.ICU: Symbol :: CurrencyDisplay
+ Language.Fluent.ICU: TimeOptions :: Text -> Maybe Bool -> Maybe Width -> Maybe Width -> Maybe Digits -> Maybe (Either Digits Width) -> Maybe Digits -> Maybe Digits -> Maybe Digits -> Maybe Digits -> Maybe Width -> TimeOptions
+ Language.Fluent.ICU: Two :: Category
+ Language.Fluent.ICU: TwoDigit :: Digits
+ Language.Fluent.ICU: Unit :: Text -> Style
+ Language.Fluent.ICU: UseIsolating :: Bool -> Override
+ Language.Fluent.ICU: Value :: Value -> PatternElement
+ Language.Fluent.ICU: WithFunction :: Identifier -> ([Pattern] -> HashMap Identifier Value -> Either String Pattern) -> Override
+ Language.Fluent.ICU: Zero :: Category
+ Language.Fluent.ICU: [arguments] :: Reference -> HashMap Text Value
+ Language.Fluent.ICU: [attribute] :: Reference -> Maybe AttributeAccessor
+ Language.Fluent.ICU: [currencyDisplay] :: NumberOptions -> CurrencyDisplay
+ Language.Fluent.ICU: [day] :: TimeOptions -> Maybe Digits
+ Language.Fluent.ICU: [era] :: TimeOptions -> Maybe Width
+ Language.Fluent.ICU: [form] :: NumberOptions -> Form
+ Language.Fluent.ICU: [functions] :: Bundle locale -> HashMap Identifier ([Pattern] -> HashMap Identifier Value -> Either String Pattern)
+ Language.Fluent.ICU: [hour12] :: TimeOptions -> Maybe Bool
+ Language.Fluent.ICU: [hour] :: TimeOptions -> Maybe Digits
+ Language.Fluent.ICU: [locales] :: Bundle locale -> NonEmpty locale
+ Language.Fluent.ICU: [maximumFractionDigits] :: NumberOptions -> Maybe Int
+ Language.Fluent.ICU: [maximumSignificantDigits] :: NumberOptions -> Maybe Int
+ Language.Fluent.ICU: [minimumFractionDigits] :: NumberOptions -> Int
+ Language.Fluent.ICU: [minimumIntegerDigits] :: NumberOptions -> Int
+ Language.Fluent.ICU: [minimumSignificantDigits] :: NumberOptions -> Maybe Int
+ Language.Fluent.ICU: [minute] :: TimeOptions -> Maybe Digits
+ Language.Fluent.ICU: [month] :: TimeOptions -> Maybe (Either Digits Width)
+ Language.Fluent.ICU: [name] :: Reference -> Either String Identifier
+ Language.Fluent.ICU: [overrides] :: Reference -> [Override]
+ Language.Fluent.ICU: [resources] :: Bundle locale -> [Resource]
+ Language.Fluent.ICU: [second] :: TimeOptions -> Maybe Digits
+ Language.Fluent.ICU: [style] :: NumberOptions -> Style
+ Language.Fluent.ICU: [timeZoneName] :: TimeOptions -> Maybe Width
+ Language.Fluent.ICU: [timeZone] :: TimeOptions -> Text
+ Language.Fluent.ICU: [unitDisplay] :: NumberOptions -> Width
+ Language.Fluent.ICU: [useGrouping] :: NumberOptions -> Bool
+ Language.Fluent.ICU: [useIsolating] :: Bundle locale -> Bool
+ Language.Fluent.ICU: [weekday] :: TimeOptions -> Maybe Width
+ Language.Fluent.ICU: [year] :: TimeOptions -> Maybe Digits
+ Language.Fluent.ICU: bundle :: NonEmpty locale -> [Resource] -> Bundle locale
+ Language.Fluent.ICU: capitalise :: Locale l => l -> Text -> Text
+ Language.Fluent.ICU: class Locale l
+ Language.Fluent.ICU: class ToValue v
+ Language.Fluent.ICU: class Translate r
+ Language.Fluent.ICU: data Bundle locale
+ Language.Fluent.ICU: data Category
+ Language.Fluent.ICU: data CurrencyDisplay
+ Language.Fluent.ICU: data Digits
+ Language.Fluent.ICU: data Form
+ Language.Fluent.ICU: data Identifier
+ Language.Fluent.ICU: data NumberOptions
+ Language.Fluent.ICU: data Override
+ Language.Fluent.ICU: data PatternElement
+ Language.Fluent.ICU: data Reference
+ Language.Fluent.ICU: data Resource
+ Language.Fluent.ICU: data Style
+ Language.Fluent.ICU: data TimeOptions
+ Language.Fluent.ICU: data Value
+ Language.Fluent.ICU: data Width
+ Language.Fluent.ICU: displayLanguage :: Locale l => l -> l -> Maybe Text
+ Language.Fluent.ICU: fluent :: QuasiQuoter
+ Language.Fluent.ICU: formatNumber :: Locale l => NonEmpty l -> Scientific -> NumberOptions -> Either String Text
+ Language.Fluent.ICU: formatTime :: Locale l => NonEmpty l -> UTCTime -> TimeOptions -> Either String Text
+ Language.Fluent.ICU: fromCode :: Locale l => Text -> Maybe l
+ Language.Fluent.ICU: localDisplayLanguage :: Locale l => l -> Maybe Text
+ Language.Fluent.ICU: messageIdentifiers :: FilePath -> DecsQ
+ Language.Fluent.ICU: newtype Pattern
+ Language.Fluent.ICU: numberOptions :: NumberOptions
+ Language.Fluent.ICU: parseResource :: Text -> Either String Resource
+ Language.Fluent.ICU: parseResourceLenient :: Text -> Resource
+ Language.Fluent.ICU: pluralCategory :: Locale l => l -> Scientific -> NumberOptions -> Category
+ Language.Fluent.ICU: timeOptions :: TimeOptions
+ Language.Fluent.ICU: toCode :: Locale l => l -> Text
+ Language.Fluent.ICU: toValue :: ToValue v => v -> Value
+ Language.Fluent.ICU: translate :: Translate r => Reference -> r
Files
- CHANGELOG.md +16/−0
- Setup.hs +0/−2
- fluent-icu.cabal +3/−3
- src/Language/Fluent/ICU.hs +22/−19
- test/BuiltinSpec.hs +3/−3
- test/Main.hs +1/−0
- test/NumberSpec.hs +25/−24
- test/Prelude.hs +5/−4
- test/Spec.hs +0/−1
- test/TimeSpec.hs +12/−10
CHANGELOG.md view
@@ -5,6 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [1.1.0] - 2026-10-20++### Changed++- **Breaking:** `fluent` 1.1.0 breaking changes:+ - `SomeValue` is now `Value`.+ - The `Value` class is now `ToValue`.+ - Constructors are used qualified: `Value.String`, `Value.Number`, `Value.Time`.+ - `CustomValue` and `ToValue.format` are replaced by `Value.Formatted`.+ - The `fluent` quasi-quoter and `messageIdentifiers` fail to compile on a syntax error.+ - `parseResource` fails on a syntax error. `parseResourceLenient` returns a `JunkEntry` instead.++### Fixed++- Huge digit options, such as `minimumIntegerDigits: 1000000000`, are capped to the ranges of `Intl.NumberFormat`.+ ## [1.0.0] - 2026-10-01 ### Added
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
fluent-icu.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: fluent-icu-version: 1.0.0+version: 1.1.0 synopsis: ICU backend for fluent description: ICU backend for <https://hackage.haskell.org/package/fluent fluent>. license: EUPL-1.2@@ -45,7 +45,7 @@ build-depends: base >=4.11 && <5,- fluent >=1.0 && <1.1,+ fluent >=1.1 && <1.2, text >=2.0 && <2.2, library@@ -69,7 +69,7 @@ import: common type: exitcode-stdio-1.0 hs-source-dirs: test- main-is: Spec.hs+ main-is: Main.hs other-modules: BuiltinSpec LocaleSpec
src/Language/Fluent/ICU.hs view
@@ -9,7 +9,7 @@ -- -- <https://projectfluent.org Project Fluent> localisation backed by -- <https://unicode-org.github.io/icu/ ICU> using the--- <https://hackage.package.org/package/text-icu text-icu> library.+-- <https://hackage.haskell.org/package/text-icu text-icu> library. -- -- This module re-exports everything needed to translate Fluent messages. --@@ -39,6 +39,8 @@ -- -- > resource <- either fail pure . parseResource =<< Text.readFile "resource.ftl" --+-- A syntax error fails the whole file. 'parseResourceLenient' returns a 'JunkEntry' instead.+-- -- == 2. Building bundles -- -- Combine 'Resource's and 'Locale's into a 'Bundle' using 'bundle':@@ -55,38 +57,39 @@ -- -- Pass parameters as name-value tuples: ----- >>> translate "unread-emails" ("count", value @Int 1) english :: Either String Text+-- >>> translate "unread-emails" ("count", 1) english :: Either String Text -- Right "You have one unread email."--- >>> translate "unread-emails" ("count", value @Int 5) english :: Either String Text+-- >>> translate "unread-emails" ("count", 5) english :: Either String Text -- Right "You have several unread emails." -- -- You can chain multiple arguments and configure Unicode isolation marks using 'UseIsolating': ----- >>> translate "greeting" ("name", value @Text "Ann") ("count", value @Int 3) (UseIsolating False) english :: Either String Text+-- >>> translate "greeting" ("name", "Ann") ("count", 3) (UseIsolating False) english :: Either String Text -- Right "Hello, Ann! You have 3 new messages." -- -- == 4. Formatting numbers, currencies, and units -- -- Number formatting adapts to the target locale: ----- >>> translate "amount" ("amount", value @Int 1234567) english :: Either String Text+-- >>> translate "amount" ("amount", 1234567 :: Int) english :: Either String Text -- Right "1,234,567"--- >>> translate "amount" ("amount", value @Int 1234567) german :: Either String Text+-- >>> translate "amount" ("amount", 1234567 :: Int) german :: Either String Text -- Right "1'234'567"--- >>> translate "price" ("amount", value @Double 1234.5) english :: Either String Text+-- >>> translate "price" ("amount", 1234.5 :: Double) english :: Either String Text -- Right "1,234.50 euros"--- >>> translate "price" ("amount", value @Double 1234.5) german :: Either String Text+-- >>> translate "price" ("amount", 1234.5 :: Double) german :: Either String Text -- Right "1'234.50 Euro"--- >>> translate "temperature" ("degrees", value @Int 21) english :: Either String Text+-- >>> translate "temperature" ("degrees", 21 :: Int) english :: Either String Text -- Right "21 degrees Celsius"--- >>> translate "temperature" ("degrees", value @Int 21) german :: Either String Text+-- >>> translate "temperature" ("degrees", 21 :: Int) german :: Either String Text -- Right "21 Grad Celsius" -- -- You can pass formatting options directly via structured values: ----- >>> translate "amount" ("amount", NumberValue 1234.5 numberOptions{style = Currency "EUR", currencyDisplay = Name}) english :: Either String Text+-- >>> import Language.Fluent.Value qualified as Value+-- >>> translate "amount" ("amount", Value.Number 1234.5 numberOptions{style = Currency "EUR", currencyDisplay = Name}) english :: Either String Text -- Right "1,234.50 euros"--- >>> translate "amount" ("amount", NumberValue 21 numberOptions{style = Unit "metre-per-second", unitDisplay = Short}) english :: Either String Text+-- >>> translate "amount" ("amount", Value.Number 21 numberOptions{style = Unit "metre-per-second", unitDisplay = Short}) english :: Either String Text -- Right "21 m/s" module Language.Fluent.ICU ( -- * Re-exports@@ -139,7 +142,7 @@ withLocaleName a \source -> withLocaleName b \target -> allocaBytes capacity \name -> do- written <- fluentGetDisplayLanguage source target name (fromIntegral capacity)+ written <- fluentGetDisplayLanguage source target name $ fromIntegral capacity if written < 0 then pure Nothing else Just . Text.decodeUtf8 <$> ByteString.packCStringLen (name, fromIntegral written)@@ -151,7 +154,7 @@ ByteString.useAsCStringLen utf8 \(source, sourceLength) -> allocaBytes capacity \out -> do written <-- fluentFormatTitlecase locale source (fromIntegral sourceLength) out (fromIntegral capacity)+ fluentFormatTitlecase locale source (fromIntegral sourceLength) out $ fromIntegral capacity if written < 0 then pure text else Text.decodeUtf8 <$> ByteString.packCStringLen (out, fromIntegral written)@@ -172,7 +175,7 @@ decimal (fromIntegral decimalLength) keyword- (fromIntegral capacity)+ $ fromIntegral capacity if written < 0 then pure Plural.Other else fromMaybe Plural.Other . readMaybe <$> peekCStringLen (keyword, fromIntegral written)@@ -195,7 +198,7 @@ decimal (fromIntegral decimalLength) out- (fromIntegral capacity)+ $ fromIntegral capacity if written < 0 then Left . ("NUMBER: " <>) <$> icuErrorMessage written else Right . Text.decodeUtf8 <$> ByteString.packCStringLen (out, fromIntegral written)@@ -209,7 +212,7 @@ withLocaleName locale \name -> withUtf8Text skeleton \wanted -> allocaBytes capacity \ptr -> do- written <- fluentResolveDatePattern name wanted ptr (fromIntegral capacity)+ written <- fluentResolveDatePattern name wanted ptr $ fromIntegral capacity if written < 0 then pure skeleton else Text.decodeUtf8 <$> ByteString.packCStringLen (ptr, fromIntegral written)@@ -223,7 +226,7 @@ skeleton = Time.skeleton options millisecondsSinceEpoch :: UTCTime -> Double- millisecondsSinceEpoch = (* 1000) . realToFrac . utcTimeToPOSIXSeconds+ millisecondsSinceEpoch = (1000 *) . realToFrac . utcTimeToPOSIXSeconds setMillis :: Calendar -> Double -> IO () setMillis calendar ms = do@@ -251,7 +254,7 @@ withMany withLocaleName (NonEmpty.toList preferences) \accept -> withArrayLen accept \count array -> allocaBytes capacity \out -> do- written <- fluentNegotiateSupportedLocale array (fromIntegral count) out (fromIntegral capacity)+ written <- fluentNegotiateSupportedLocale array (fromIntegral count) out $ fromIntegral capacity if written < 0 then pure Nothing else Just . ICU.Locale <$> peekCStringLen (out, fromIntegral written)
test/BuiltinSpec.hs view
@@ -13,7 +13,7 @@ it "omits the grouping separators when turned off" $ translate "foo"- [("arg", number 1234567)]+ [("arg", 1234567 :: Int)] [fluent|foo = { NUMBER($arg, useGrouping: "false") }|] `shouldBe` Right "1234567" it "selects by the plural category of the number" $@@ -66,10 +66,10 @@ `shouldBe` Left "DATETIME: the positional argument is not a time" written :: Resource -> Either String Text-written = translate "foo" [("arg", number 3)]+written = translate "foo" [("arg", 3 :: Int)] when' :: Resource -> Either String Text-when' = translate "foo" [("arg", value afternoon)]+when' = translate "foo" [("arg", afternoon)] -- | 2026-08-10, a Monday, 12:34:56 UTC. afternoon :: UTCTime
+ test/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
test/NumberSpec.hs view
@@ -1,94 +1,95 @@ module NumberSpec (spec) where +import Language.Fluent.Value qualified as Value import Prelude spec :: Spec spec = do it "groups the digits the way the locale groups them" $- translate "foo" ("arg", NumberValue 1234567 numberOptions) resource `shouldBe` Right "1,234,567"+ translate "foo" ("arg", Value.Number 1234567 numberOptions) resource `shouldBe` Right "1,234,567" it "leaves the grouping out when it is turned off" $- translate "foo" ("arg", NumberValue 1234567 numberOptions{useGrouping = False}) resource+ translate "foo" ("arg", Value.Number 1234567 numberOptions{useGrouping = False}) resource `shouldBe` Right "1234567" it "pads the integer part to the given width" $- translate "foo" ("arg", NumberValue 5 numberOptions{minimumIntegerDigits = 3}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{minimumIntegerDigits = 3}) resource `shouldBe` Right "005" it "limits the fraction digits to the maximum given" $- translate "foo" ("arg", NumberValue 1.5 numberOptions{maximumFractionDigits = Just 0}) resource+ translate "foo" ("arg", Value.Number 1.5 numberOptions{maximumFractionDigits = Just 0}) resource `shouldBe` Right "2" it "limits the significant digits it is given" $ translate "foo"- ("arg", NumberValue 1234 numberOptions{maximumSignificantDigits = Just 2})+ ("arg", Value.Number 1234 numberOptions{maximumSignificantDigits = Just 2}) resource `shouldBe` Right "1,200" it "is shown as a percentage" $- translate "foo" ("arg", NumberValue 0.25 numberOptions{style = Percent}) resource+ translate "foo" ("arg", Value.Number 0.25 numberOptions{style = Percent}) resource `shouldBe` Right "25%" it "is an amount of a currency" $- translate "foo" ("arg", NumberValue 12.5 numberOptions{style = Currency "EUR"}) resource+ translate "foo" ("arg", Value.Number 12.5 numberOptions{style = Currency "EUR"}) resource `shouldBe` Right "€12.50" it "spells the currency's name out, not its symbol" $ translate "foo"- ("arg", NumberValue 12.5 numberOptions{style = Currency "EUR", currencyDisplay = Name})+ ("arg", Value.Number 12.5 numberOptions{style = Currency "EUR", currencyDisplay = Name}) resource `shouldBe` Right "12.50 euros" it "is an amount of a unit" $- translate "foo" ("arg", NumberValue 5 numberOptions{style = Unit "kilometre-per-hour"}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{style = Unit "kilometre-per-hour"}) resource `shouldBe` Right "5 km/h" it "is an amount of a unit of one thing per another" $- translate "foo" ("arg", NumberValue 5 numberOptions{style = Unit "litre-per-kilometre"}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{style = Unit "litre-per-kilometre"}) resource `shouldBe` Right "5 l/km" it "shows the unit's symbol when narrow, its name when long" $ do translate "foo"- ("arg", NumberValue 5 numberOptions{style = Unit "litre", unitDisplay = Narrow})+ ("arg", Value.Number 5 numberOptions{style = Unit "litre", unitDisplay = Narrow}) resource `shouldBe` Right "5l" translate "foo"- ("arg", NumberValue 5 numberOptions{style = Unit "litre", unitDisplay = Long})+ ("arg", Value.Number 5 numberOptions{style = Unit "litre", unitDisplay = Long}) resource `shouldBe` Right "5 litres" it "accepts a unit identifier written in British spelling" $ do- translate "foo" ("arg", NumberValue 5 numberOptions{style = Unit "litre"}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{style = Unit "litre"}) resource `shouldBe` Right "5 l"- translate "foo" ("arg", NumberValue 5 numberOptions{style = Unit "millilitre"}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{style = Unit "millilitre"}) resource `shouldBe` Right "5 ml"- translate "foo" ("arg", NumberValue 5 numberOptions{style = Unit "square-kilometre"}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{style = Unit "square-kilometre"}) resource `shouldBe` Right "5 km²" it "keeps the fraction digits a unit is given" $ translate "foo"- ("arg", NumberValue 5 numberOptions{style = Unit "megabyte", minimumFractionDigits = 1})+ ("arg", Value.Number 5 numberOptions{style = Unit "megabyte", minimumFractionDigits = 1}) resource `shouldBe` Right "5.0 MB" it "spells the unit the way the locale spells it, whatever it is called" $ do translate "foo"- ("arg", NumberValue 5 numberOptions{style = Unit "kilometre", unitDisplay = Long})+ ("arg", Value.Number 5 numberOptions{style = Unit "kilometre", unitDisplay = Long}) resource `shouldBe` Right "5 kilometres" translate "foo"- ("arg", NumberValue 5 numberOptions{style = Unit "litre", unitDisplay = Long})+ ("arg", Value.Number 5 numberOptions{style = Unit "litre", unitDisplay = Long}) deCH resource `shouldBe` Right "5 Liter" it "rejects a unit it does not know" $- translate "foo" ("arg", NumberValue 5 numberOptions{style = Unit "banana"}) resource+ translate "foo" ("arg", Value.Number 5 numberOptions{style = Unit "banana"}) resource `shouldBe` Left "NUMBER: U_NUMBER_SKELETON_SYNTAX_ERROR" it "uses the first known locale in the chain" $ do- translate "foo" ("arg", NumberValue 1234.5 numberOptions) [deCH, enGB] resource+ translate "foo" ("arg", Value.Number 1234.5 numberOptions) [deCH, enGB] resource `shouldBe` Right "1'234.5"- translate "foo" ("arg", NumberValue 1234.5 numberOptions) [enGB, deCH] resource+ translate "foo" ("arg", Value.Number 1234.5 numberOptions) [enGB, deCH] resource `shouldBe` Right "1,234.5"- translate "foo" ("arg", NumberValue 1234.5 numberOptions) [unknown, deCH] resource+ translate "foo" ("arg", Value.Number 1234.5 numberOptions) [unknown, deCH] resource `shouldBe` Right "1'234.5" it "falls back to an ancestor locale when the exact one is unknown" $ do- translate "foo" ("arg", NumberValue 1234.5 numberOptions) deSince1996 resource+ translate "foo" ("arg", Value.Number 1234.5 numberOptions) deSince1996 resource `shouldBe` Right "1'234.5"- translate "foo" ("arg", NumberValue 1234.5 numberOptions) deInAnUnknownScript resource+ translate "foo" ("arg", Value.Number 1234.5 numberOptions) deInAnUnknownScript resource `shouldBe` Right "1.234,5" resource :: Resource
test/Prelude.hs view
@@ -21,6 +21,7 @@ import Language.Fluent.Bundle qualified as Bundle import Language.Fluent.ICU import Language.Fluent.Locale qualified as Locale+import Language.Fluent.Value qualified as Value import Test.Hspec import "base" Prelude @@ -40,12 +41,12 @@ instance (r ~ Either String Text) => Translate (Resource -> r) where translate arguments = translate arguments $ Locale.fromCode @LocaleName "en-GB" -number :: Scientific -> SomeValue+number :: Scientific -> Value number = numberWith Cardinal 0 -ordinal :: Scientific -> SomeValue+ordinal :: Scientific -> Value ordinal = numberWith Ordinal 0 -numberWith :: Form -> Int -> Scientific -> SomeValue+numberWith :: Form -> Int -> Scientific -> Value numberWith form minimumFractionDigits amount =- NumberValue amount numberOptions{form, minimumFractionDigits}+ Value.Number amount numberOptions{form, minimumFractionDigits}
− test/Spec.hs
@@ -1,1 +0,0 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
test/TimeSpec.hs view
@@ -1,43 +1,45 @@ module TimeSpec (spec) where import Data.Time+import Language.Fluent.Value qualified as Value import Prelude spec :: Spec spec = do it "is a date of numbers when given nothing" $- translate "foo" ("when", TimeValue afternoon timeOptions) resource `shouldBe` Right "10/08/2026"+ translate "foo" ("when", Value.Time afternoon timeOptions) resource `shouldBe` Right "10/08/2026" it "formats the date to match the locale's convention" $- translate "foo" ("when", TimeValue afternoon timeOptions) deCH resource `shouldBe` Right "10.8.2026"+ translate "foo" ("when", Value.Time afternoon timeOptions) deCH resource+ `shouldBe` Right "10.8.2026" it "spells the month out at the width it is given" $ translate "foo"- ("when", TimeValue afternoon dated{month = Just (Right Long)})+ ("when", Value.Time afternoon dated{month = Just $ Right Long}) resource `shouldBe` Right "10 August 2026" it "formats only the field it is given" $- translate "foo" ("when", TimeValue afternoon timeOptions{month = Just (Right Long)}) resource+ translate "foo" ("when", Value.Time afternoon timeOptions{month = Just $ Right Long}) resource `shouldBe` Right "August" it "spells the weekday out" $- translate "foo" ("when", TimeValue afternoon timeOptions{weekday = Just Long}) resource+ translate "foo" ("when", Value.Time afternoon timeOptions{weekday = Just Long}) resource `shouldBe` Right "Monday" it "shows the time of day it is given" $- translate "foo" ("when", TimeValue afternoon clock) resource `shouldBe` Right "12:34"+ translate "foo" ("when", Value.Time afternoon clock) resource `shouldBe` Right "12:34" it "formats the month in digits or in words, beside a year of digits" $ do translate "foo"- ("when", TimeValue afternoon timeOptions{year = Just Numeric, month = Just (Right Narrow)})+ ("when", Value.Time afternoon timeOptions{year = Just Numeric, month = Just $ Right Narrow}) resource `shouldBe` Right "A 2026" translate "foo"- ("when", TimeValue afternoon timeOptions{year = Just Numeric, month = Just (Left TwoDigit)})+ ("when", Value.Time afternoon timeOptions{year = Just Numeric, month = Just $ Left TwoDigit}) resource `shouldBe` Right "08/2026" it "reads the time in the zone it is given" $- translate "foo" ("when", TimeValue afternoon berlin) resource `shouldBe` Right "14:34"+ translate "foo" ("when", Value.Time afternoon berlin) resource `shouldBe` Right "14:34" it "keeps the hour the zone keeps at that time of year" $- translate "foo" ("when", TimeValue midwinter berlin) resource `shouldBe` Right "13:34"+ translate "foo" ("when", Value.Time midwinter berlin) resource `shouldBe` Right "13:34" resource :: Resource resource = [fluent|foo = { $when }|]