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 +21/−0
- Setup.hs +0/−2
- miso-fluent.cabal +3/−3
- src/Miso/Fluent.hs +11/−6
- src/Miso/Fluent/Intl.hs +1/−1
- src/Miso/Fluent/Parser.hs +17/−3
- test/Main.hs +47/−46
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