packages feed

miso-fluent 1.0.0 → 1.1.0

raw patch · 7 files changed

+100/−61 lines, 7 filesdep ~fluentdep ~fluent-syntaxsetup-changedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: fluent, fluent-syntax

API changes (from Hackage documentation)

- Miso.Fluent: CustomValue :: v -> CustomValue
- Miso.Fluent: NumberValue :: Scientific -> NumberOptions -> SomeValue
- Miso.Fluent: SomeValue :: CustomValue -> SomeValue
- Miso.Fluent: StringValue :: Text -> SomeValue
- Miso.Fluent: TimeValue :: UTCTime -> TimeOptions -> SomeValue
- Miso.Fluent: class Value v
- Miso.Fluent: data CustomValue
- Miso.Fluent: data SomeValue
- Miso.Fluent: format :: (Value v, Locale l) => NonEmpty l -> v -> Either String Text
- Miso.Fluent: instance Miso.String.FromMisoString Language.Fluent.Value.SomeValue
- Miso.Fluent: value :: Value v => v -> SomeValue
+ Miso.Fluent: Few :: Category
+ Miso.Fluent: Many :: Category
+ Miso.Fluent: One :: Category
+ Miso.Fluent: Other :: Category
+ Miso.Fluent: Two :: Category
+ Miso.Fluent: Zero :: Category
+ Miso.Fluent: class ToValue v
+ Miso.Fluent: data Category
+ Miso.Fluent: data Value
+ Miso.Fluent: instance Miso.String.FromMisoString Language.Fluent.Value.Value
+ Miso.Fluent: parseResourceLenient :: MisoString -> Resource
+ Miso.Fluent: toValue :: ToValue v => v -> Value
+ Miso.Fluent.Parser: parseResource :: MisoString -> Either String Resource
+ Miso.Fluent.Parser: parseResourceLenient :: MisoString -> Resource
- Miso.Fluent: Bundle :: NonEmpty locale -> [Resource] -> HashMap Identifier ([Pattern] -> HashMap Identifier SomeValue -> Either String Pattern) -> Bool -> Bundle locale
+ Miso.Fluent: Bundle :: NonEmpty locale -> [Resource] -> HashMap Identifier ([Pattern] -> HashMap Identifier Value -> Either String Pattern) -> Bool -> Bundle locale
- Miso.Fluent: Reference :: Either String Identifier -> Maybe AttributeAccessor -> HashMap Text SomeValue -> [Override] -> Reference
+ Miso.Fluent: Reference :: Either String Identifier -> Maybe AttributeAccessor -> HashMap Text Value -> [Override] -> Reference
- Miso.Fluent: Value :: SomeValue -> PatternElement
+ Miso.Fluent: Value :: Value -> PatternElement
- Miso.Fluent: WithFunction :: Identifier -> ([Pattern] -> HashMap Identifier SomeValue -> Either String Pattern) -> Override
+ Miso.Fluent: WithFunction :: Identifier -> ([Pattern] -> HashMap Identifier Value -> Either String Pattern) -> Override
- Miso.Fluent: [arguments] :: Reference -> HashMap Text SomeValue
+ Miso.Fluent: [arguments] :: Reference -> HashMap Text Value
- Miso.Fluent: [functions] :: Bundle locale -> HashMap Identifier ([Pattern] -> HashMap Identifier SomeValue -> Either String Pattern)
+ Miso.Fluent: [functions] :: Bundle locale -> HashMap Identifier ([Pattern] -> HashMap Identifier Value -> Either String Pattern)
- Miso.Fluent: parseResource :: Text -> Either String Resource
+ Miso.Fluent: parseResource :: MisoString -> Either String Resource

Files

CHANGELOG.md view
@@ -5,6 +5,27 @@ 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++### Added++- `Miso.Fluent.Parser.parseResource` and `parseResourceLenient`.++### 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.+- **Breaking:** `Miso.Fluent` exports `parseResource` of a `MisoString`, not `Text`.++### Fixed++- Digit options outside the ranges of `Intl.NumberFormat` no longer throw a `RangeError`.+ ## [1.0.0] - 2026-10-01  ### Added
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
miso-fluent.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: miso-fluent-version: 1.0.0+version: 1.1.0 synopsis: Translate miso apps with Project Fluent description:   In-browser <https://projectfluent.org Project Fluent> localisation backed by@@ -56,6 +56,7 @@    build-depends:     base >=4 && <5,+    fluent >=1.1 && <1.2,     miso >=1.14 && <1.15,     time >=1.12 && <1.17, @@ -63,8 +64,7 @@   import: common   hs-source-dirs: src   build-depends:-    fluent >=1.0 && <1.1,-    fluent-syntax >=1.0 && <1.1,+    fluent-syntax >=1.1 && <1.2,     scientific >=0.3 && <0.4,     text >=2.1 && <2.2, 
src/Miso/Fluent.hs view
@@ -28,10 +28,11 @@ -- >            welcome = Welcome, { $name } -- >         |] ----- Or parse Fluent source at runtime from an asynchronous 'getText' request:+-- Or parse Fluent source at runtime from an asynchronous 'getText' request that decodes the body+-- using 'parseResource' or 'parseResourceLenient': -- -- > getText "/static/en-GB.ftl" []--- >     (\res -> SetResource $ Fluent.parse Fluent.resource (body res))+-- >     (\res -> SetResource . parseResource $ body res) -- >     (\err -> SetResource $ Left "Failed to fetch .ftl file") -- -- == 2. Building bundles@@ -86,6 +87,8 @@ module Miso.Fluent     ( Fluent (..)     , Locale (..)+    , parseResource+    , parseResourceLenient        -- * Re-exports     , module Language.Fluent@@ -95,8 +98,10 @@ import Data.String (IsString (..)) import Data.Text (Text) import GHC.Records (HasField)-import Language.Fluent hiding (Locale)+import Language.Fluent hiding (Locale, parseResource, parseResourceLenient)+import Language.Fluent.Value qualified as Value import Miso.Fluent.Intl (Locale (..))+import Miso.Fluent.Parser (parseResource, parseResourceLenient) import Miso.Prelude hiding (context, props) import Miso.String (FromMisoString (..)) import Miso.Trace (traceErrorId)@@ -112,14 +117,14 @@ instance Fluent (Bundle Locale) where     getBundle = id -instance FromMisoString SomeValue where-    fromMisoStringEither = fmap StringValue . fromMisoStringEither+instance FromMisoString Value where+    fromMisoStringEither = fmap Value.String . fromMisoStringEither  instance FromMisoString Reference where     fromMisoStringEither = Right . fromString . fromMisoString  #ifndef VANILLA-instance Value MisoString where value = value . fromMisoString @Text+instance ToValue MisoString where toValue = toValue . fromMisoString @Text #endif  instance {-# OVERLAPPABLE #-} (Fluent context) => Translate (context -> Either String MisoString) where
src/Miso/Fluent/Intl.hs view
@@ -143,7 +143,7 @@  digits :: NumberOptions -> [Pair] digits options =-    ["minimumIntegerDigits" .= options.minimumIntegerDigits | options.minimumIntegerDigits > 1]+    ["minimumIntegerDigits" .= Number.integerDigits options]         <> ["useGrouping" .= False | not options.useGrouping]         <> precision   where
src/Miso/Fluent/Parser.hs view
@@ -1,10 +1,24 @@-module Miso.Fluent.Parser (parse, Fluent.resource) where+module Miso.Fluent.Parser+    ( parse+    , parseResource+    , parseResourceLenient+    , Fluent.resource+    )+where  import Data.Text (Text)+import Language.Fluent.AST (Resource) import Language.Fluent.Parser (Parser) import Language.Fluent.Parser qualified as Fluent-import Language.Fluent.Parser qualified as Parser import Miso.Prelude  parse :: Parser a -> MisoString -> Either String a-parse = (. fromMisoString @Text) . Parser.parse+parse = (. fromMisoString @Text) . Fluent.parse++-- | 'Fluent.parseResource' of a 'MisoString'.+parseResource :: MisoString -> Either String Resource+parseResource = Fluent.parseResource . fromMisoString @Text++-- | 'Fluent.parseResourceLenient' of a 'MisoString'.+parseResourceLenient :: MisoString -> Resource+parseResourceLenient = Fluent.parseResourceLenient . fromMisoString @Text
test/Main.hs view
@@ -4,6 +4,7 @@  import Data.Either (isLeft) import Data.Time (UTCTime (..), fromGregorian, secondsToDiffTime)+import Language.Fluent.Value qualified as Value import Miso.Fluent import Miso.Prelude import Test.Hspec@@ -40,65 +41,65 @@     describe "Number" do         describe "grouping" do             it "grouped digits"-                $ translate "value" ("arg", NumberValue 1234567 numberOptions) enGB resource+                $ translate "value" ("arg", Value.Number 1234567 numberOptions) enGB resource                 `shouldBe` Right "1,234,567"             it "no grouping"-                $ translate "value" ("arg", NumberValue 1234567 numberOptions{useGrouping = False}) enGB resource+                $ translate "value" ("arg", Value.Number 1234567 numberOptions{useGrouping = False}) enGB resource                 `shouldBe` Right "1234567"         describe "digits" do             it "at least three integer digits"-                $ translate "value" ("arg", NumberValue 5 numberOptions{minimumIntegerDigits = 3}) enGB resource+                $ translate "value" ("arg", Value.Number 5 numberOptions{minimumIntegerDigits = 3}) enGB resource                 `shouldBe` Right "005"             it "no fraction digits"                 $ translate                     "value"-                    ("arg", NumberValue 1.5 numberOptions{maximumFractionDigits = Just 0})+                    ("arg", Value.Number 1.5 numberOptions{maximumFractionDigits = Just 0})                     enGB                     resource                 `shouldBe` Right "2"             it "two significant digits"                 $ translate                     "value"-                    ("arg", NumberValue 1234 numberOptions{maximumSignificantDigits = Just 2})+                    ("arg", Value.Number 1234 numberOptions{maximumSignificantDigits = Just 2})                     enGB                     resource                 `shouldBe` Right "1,200"             it "at least four significant digits"                 $ translate                     "value"-                    ("arg", NumberValue 1.5 numberOptions{minimumSignificantDigits = Just 4})+                    ("arg", Value.Number 1.5 numberOptions{minimumSignificantDigits = Just 4})                     enGB                     resource                 `shouldBe` Right "1.500"         describe "style" do             it "a percentage"-                $ translate "value" ("arg", NumberValue 0.25 numberOptions{style = Percent}) enGB resource+                $ translate "value" ("arg", Value.Number 0.25 numberOptions{style = Percent}) enGB resource                 `shouldBe` Right "25%"             it "an amount of money"-                $ translate "value" ("arg", NumberValue 12.5 numberOptions{style = Currency "EUR"}) enGB resource+                $ translate "value" ("arg", Value.Number 12.5 numberOptions{style = Currency "EUR"}) enGB resource                 `shouldBe` Right "€12.50"             it "an amount of a unit"                 $ translate                     "value"-                    ("arg", NumberValue 5 numberOptions{style = Unit "kilometre-per-hour"})+                    ("arg", Value.Number 5 numberOptions{style = Unit "kilometre-per-hour"})                     enGB                     resource                 `shouldBe` Right "5 km/h"             it "an unknown unit"-                $ shouldFail-                $ translate "value" ("arg", NumberValue 5 numberOptions{style = Unit "banana"}) enGB resource+                . shouldFail+                $ translate "value" ("arg", Value.Number 5 numberOptions{style = Unit "banana"}) enGB resource         describe "currency" do             it "a code beside the amount"                 $ translate                     "value"-                    ("arg", NumberValue 12.5 numberOptions{style = Currency "EUR", currencyDisplay = Code})+                    ("arg", Value.Number 12.5 numberOptions{style = Currency "EUR", currencyDisplay = Code})                     enGB                     resource                 `shouldBe` Right "EUR\160\&12.50"             it "a name beside the amount"                 $ translate                     "value"-                    ("arg", NumberValue 12.5 numberOptions{style = Currency "EUR", currencyDisplay = Name})+                    ("arg", Value.Number 12.5 numberOptions{style = Currency "EUR", currencyDisplay = Name})                     enGB                     resource                 `shouldBe` Right "12.50 euros"@@ -106,140 +107,140 @@             it "narrow and long names" do                 translate                     "value"-                    ("arg", NumberValue 5 numberOptions{style = Unit "litre", unitDisplay = Narrow})+                    ("arg", Value.Number 5 numberOptions{style = Unit "litre", unitDisplay = Narrow})                     enGB                     resource                     `shouldBe` Right "5l"                 translate                     "value"-                    ("arg", NumberValue 5 numberOptions{style = Unit "litre", unitDisplay = Long})+                    ("arg", Value.Number 5 numberOptions{style = Unit "litre", unitDisplay = Long})                     enGB                     resource                     `shouldBe` Right "5 litres"             it "the locale's own name"                 $ translate                     "value"-                    ("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 "the locale's own format"             $ translate                 "value"-                ("arg", NumberValue 1234.5 numberOptions{minimumFractionDigits = 1})+                ("arg", Value.Number 1234.5 numberOptions{minimumFractionDigits = 1})                 deCH                 resource             `shouldBe` Right "1'234.5"      describe "Plural" do         it "en-GB" do-            translate "counted" ("arg", NumberValue 1 numberOptions) enGB resource+            translate "counted" ("arg", Value.Number 1 numberOptions) enGB resource                 `shouldBe` Right "one"-            translate "counted" ("arg", NumberValue 2 numberOptions) enGB resource+            translate "counted" ("arg", Value.Number 2 numberOptions) enGB resource                 `shouldBe` Right "other"         it "ja" do-            translate "counted" ("arg", NumberValue 1 numberOptions) ja resource+            translate "counted" ("arg", Value.Number 1 numberOptions) ja resource                 `shouldBe` Right "other"-            translate "counted" ("arg", NumberValue 2 numberOptions) ja resource+            translate "counted" ("arg", Value.Number 2 numberOptions) ja resource                 `shouldBe` Right "other"-            translate "counted" ("arg", NumberValue 5 numberOptions) ja resource+            translate "counted" ("arg", Value.Number 5 numberOptions) ja resource                 `shouldBe` Right "other"         it "ordinals" do-            translate "counted" ("arg", NumberValue 1 ordinal) enGB resource+            translate "counted" ("arg", Value.Number 1 ordinal) enGB resource                 `shouldBe` Right "one"-            translate "counted" ("arg", NumberValue 2 ordinal) enGB resource+            translate "counted" ("arg", Value.Number 2 ordinal) enGB resource                 `shouldBe` Right "two"-            translate "counted" ("arg", NumberValue 3 ordinal) enGB resource+            translate "counted" ("arg", Value.Number 3 ordinal) enGB resource                 `shouldBe` Right "few"-            translate "counted" ("arg", NumberValue 4 ordinal) enGB resource+            translate "counted" ("arg", Value.Number 4 ordinal) enGB resource                 `shouldBe` Right "other"      describe "Time" do         it "a date of numbers"-            $ translate "value" ("arg", TimeValue afternoon timeOptions) enGB resource+            $ translate "value" ("arg", Value.Time afternoon timeOptions) enGB resource             `shouldBe` Right "10/08/2026"         it "the locale's own order"-            $ translate "value" ("arg", TimeValue afternoon timeOptions) deCH resource+            $ translate "value" ("arg", Value.Time afternoon timeOptions) deCH resource             `shouldBe` Right "10.8.2026"         describe "month" do             it "a long name"                 $ translate                     "value"-                    ("arg", TimeValue afternoon dated{month = Just (Right Long)})+                    ("arg", Value.Time afternoon dated{month = Just $ Right Long})                     enGB                     resource                 `shouldBe` Right "10 August 2026"             it "a long name on its own"                 $ translate                     "value"-                    ("arg", TimeValue afternoon timeOptions{month = Just (Right Long)})+                    ("arg", Value.Time afternoon timeOptions{month = Just $ Right Long})                     enGB                     resource                 `shouldBe` Right "August"             it "short and narrow names" do-                translate "value" ("arg", TimeValue afternoon dated{month = Just (Right Short)}) enGB resource+                translate "value" ("arg", Value.Time afternoon dated{month = Just $ Right Short}) enGB resource                     `shouldBe` Right "10 Aug 2026"                 translate                     "value"-                    ("arg", TimeValue afternoon timeOptions{month = Just (Right Narrow)})+                    ("arg", Value.Time afternoon timeOptions{month = Just $ Right Narrow})                     enGB                     resource                     `shouldBe` Right "A"         it "weekday names" do-            translate "value" ("arg", TimeValue afternoon timeOptions{weekday = Just Long}) enGB resource+            translate "value" ("arg", Value.Time afternoon timeOptions{weekday = Just Long}) enGB resource                 `shouldBe` Right "Monday"-            translate "value" ("arg", TimeValue afternoon timeOptions{weekday = Just Short}) enGB resource+            translate "value" ("arg", Value.Time afternoon timeOptions{weekday = Just Short}) enGB resource                 `shouldBe` Right "Mon"-            translate "value" ("arg", TimeValue afternoon timeOptions{weekday = Just Narrow}) enGB resource+            translate "value" ("arg", Value.Time afternoon timeOptions{weekday = Just Narrow}) enGB resource                 `shouldBe` Right "M"         it "a two-digit year and day" do-            translate "value" ("arg", TimeValue afternoon timeOptions{year = Just TwoDigit}) enGB resource+            translate "value" ("arg", Value.Time afternoon timeOptions{year = Just TwoDigit}) enGB resource                 `shouldBe` Right "26"-            translate "value" ("arg", TimeValue afternoon timeOptions{day = Just TwoDigit}) enGB resource+            translate "value" ("arg", Value.Time afternoon timeOptions{day = Just TwoDigit}) enGB resource                 `shouldBe` Right "10"         it "an era"             $ translate                 "value"-                ("arg", TimeValue afternoon timeOptions{era = Just Short, year = Just Numeric})+                ("arg", Value.Time afternoon timeOptions{era = Just Short, year = Just Numeric})                 enGB                 resource             `shouldBe` Right "2026 AD"         describe "clock" do             it "an hour and a minute"-                $ translate "value" ("arg", TimeValue afternoon clock{hour12 = Just False}) enGB resource+                $ translate "value" ("arg", Value.Time afternoon clock{hour12 = Just False}) enGB resource                 `shouldBe` Right "12:34"             it "seconds"                 $ translate                     "value"-                    ("arg", TimeValue afternoon clock{second = Just TwoDigit, hour12 = Just False})+                    ("arg", Value.Time afternoon clock{second = Just TwoDigit, hour12 = Just False})                     enGB                     resource                 `shouldBe` Right "12:34:56"             it "the locale's own clock"-                $ translate "value" ("arg", TimeValue afternoon clock) enGB resource+                $ translate "value" ("arg", Value.Time afternoon clock) enGB resource                 `shouldBe` Right "12:34"             it "a twelve-hour clock"-                $ translate "value" ("arg", TimeValue afternoon clock{hour12 = Just True}) enGB resource+                $ translate "value" ("arg", Value.Time afternoon clock{hour12 = Just True}) enGB resource                 `shouldBe` Right "12:34 pm"         describe "time zone" do             it "short and long names" do                 translate                     "value"-                    ("arg", TimeValue afternoon clock{timeZoneName = Just Short, hour12 = Just False})+                    ("arg", Value.Time afternoon clock{timeZoneName = Just Short, hour12 = Just False})                     enGB                     resource                     `shouldBe` Right "12:34 UTC"                 translate                     "value"-                    ("arg", TimeValue afternoon clock{timeZoneName = Just Long, hour12 = Just False})+                    ("arg", Value.Time afternoon clock{timeZoneName = Just Long, hour12 = Just False})                     enGB                     resource                     `shouldBe` Right "12:34 Coordinated Universal Time"             it "another zone"-                $ translate "value" ("arg", TimeValue afternoon berlin) enGB resource+                $ translate "value" ("arg", Value.Time afternoon berlin) enGB resource                 `shouldBe` Right "14:34"             it "another zone in winter"-                $ translate "value" ("arg", TimeValue midwinter berlin) enGB resource+                $ translate "value" ("arg", Value.Time midwinter berlin) enGB resource                 `shouldBe` Right "13:34"      describe "Translations" do