packages feed

miso-fluent-1.0.0: test/Main.hs

{-# LANGUAGE CPP #-}

module Main (main) where

import Data.Either (isLeft)
import Data.Time (UTCTime (..), fromGregorian, secondsToDiffTime)
import Miso.Fluent
import Miso.Prelude
import Test.Hspec
    ( Expectation
    , HasCallStack
    , Spec
    , describe
    , hspec
    , it
    , shouldBe
    , shouldSatisfy
    )

#ifdef WASM
foreign export javascript "hs_start" main :: IO ()
#endif

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
    describe "Locale" do
        describe "from a tag" do
            it "known" do
                fromCode "en-GB" `shouldBe` Just (Locale "en-GB")
                fromCode "ja" `shouldBe` Just (Locale "ja")
            it "mixed case" $ fromCode "EN-gb" `shouldBe` Just (Locale "en-GB")
            it "unknown" $ fromCode "banana" `shouldBe` (Nothing :: Maybe Locale)
            it "no tag at all" $ fromCode "en_GB" `shouldBe` (Nothing :: Maybe Locale)
        it "display language" $ displayLanguage ja deCH `shouldBe` Just "Japanisch"
        it "local display language" $ localDisplayLanguage ja `shouldBe` Just "日本語"

    describe "Number" do
        describe "grouping" do
            it "grouped digits"
                $ translate "value" ("arg", NumberValue 1234567 numberOptions) enGB resource
                `shouldBe` Right "1,234,567"
            it "no grouping"
                $ translate "value" ("arg", NumberValue 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
                `shouldBe` Right "005"
            it "no fraction digits"
                $ translate
                    "value"
                    ("arg", NumberValue 1.5 numberOptions{maximumFractionDigits = Just 0})
                    enGB
                    resource
                `shouldBe` Right "2"
            it "two significant digits"
                $ translate
                    "value"
                    ("arg", NumberValue 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})
                    enGB
                    resource
                `shouldBe` Right "1.500"
        describe "style" do
            it "a percentage"
                $ translate "value" ("arg", NumberValue 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
                `shouldBe` Right "€12.50"
            it "an amount of a unit"
                $ translate
                    "value"
                    ("arg", NumberValue 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
        describe "currency" do
            it "a code beside the amount"
                $ translate
                    "value"
                    ("arg", NumberValue 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})
                    enGB
                    resource
                `shouldBe` Right "12.50 euros"
        describe "unit" do
            it "narrow and long names" do
                translate
                    "value"
                    ("arg", NumberValue 5 numberOptions{style = Unit "litre", unitDisplay = Narrow})
                    enGB
                    resource
                    `shouldBe` Right "5l"
                translate
                    "value"
                    ("arg", NumberValue 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})
                    deCH
                    resource
                `shouldBe` Right "5 Liter"
        it "the locale's own format"
            $ translate
                "value"
                ("arg", NumberValue 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
                `shouldBe` Right "one"
            translate "counted" ("arg", NumberValue 2 numberOptions) enGB resource
                `shouldBe` Right "other"
        it "ja" do
            translate "counted" ("arg", NumberValue 1 numberOptions) ja resource
                `shouldBe` Right "other"
            translate "counted" ("arg", NumberValue 2 numberOptions) ja resource
                `shouldBe` Right "other"
            translate "counted" ("arg", NumberValue 5 numberOptions) ja resource
                `shouldBe` Right "other"
        it "ordinals" do
            translate "counted" ("arg", NumberValue 1 ordinal) enGB resource
                `shouldBe` Right "one"
            translate "counted" ("arg", NumberValue 2 ordinal) enGB resource
                `shouldBe` Right "two"
            translate "counted" ("arg", NumberValue 3 ordinal) enGB resource
                `shouldBe` Right "few"
            translate "counted" ("arg", NumberValue 4 ordinal) enGB resource
                `shouldBe` Right "other"

    describe "Time" do
        it "a date of numbers"
            $ translate "value" ("arg", TimeValue afternoon timeOptions) enGB resource
            `shouldBe` Right "10/08/2026"
        it "the locale's own order"
            $ translate "value" ("arg", TimeValue 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)})
                    enGB
                    resource
                `shouldBe` Right "10 August 2026"
            it "a long name on its own"
                $ translate
                    "value"
                    ("arg", TimeValue 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
                    `shouldBe` Right "10 Aug 2026"
                translate
                    "value"
                    ("arg", TimeValue 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
                `shouldBe` Right "Monday"
            translate "value" ("arg", TimeValue afternoon timeOptions{weekday = Just Short}) enGB resource
                `shouldBe` Right "Mon"
            translate "value" ("arg", TimeValue 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
                `shouldBe` Right "26"
            translate "value" ("arg", TimeValue 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})
                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
                `shouldBe` Right "12:34"
            it "seconds"
                $ translate
                    "value"
                    ("arg", TimeValue 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
                `shouldBe` Right "12:34"
            it "a twelve-hour clock"
                $ translate "value" ("arg", TimeValue 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})
                    enGB
                    resource
                    `shouldBe` Right "12:34 UTC"
                translate
                    "value"
                    ("arg", TimeValue 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
                `shouldBe` Right "14:34"
            it "another zone in winter"
                $ translate "value" ("arg", TimeValue midwinter berlin) enGB resource
                `shouldBe` Right "13:34"

    describe "Translations" do
        it "MisoString"
            $ translate "hello" (bundle (pure enGB) $ pure resource)
            `shouldBe` ("Hello" :: MisoString)
        it "View" $ (translate "hello" :: View (Bundle Locale) () () ()) `seq` pure @IO ()

shouldFail :: (HasCallStack) => Either String MisoString -> Expectation
shouldFail actual = actual `shouldSatisfy` isLeft

resource :: Resource
resource =
    [fluent|
      hello = Hello
      value = { $arg }
      counted = { $arg ->
          [one] one
          [two] two
          [few] few
          [many] many
         *[other] other
        }
    |]

instance (r ~ Either String MisoString) => Translate (Locale -> Resource -> r) where
    translate reference locale = translate reference . bundle (pure locale) . pure

enGB, deCH, ja :: Locale
enGB = Locale "en-GB"
deCH = Locale "de-CH"
ja = Locale "ja"

ordinal :: NumberOptions
ordinal = numberOptions{form = Ordinal}

dated :: TimeOptions
dated = timeOptions{day = Just Numeric, year = Just Numeric}

clock :: TimeOptions
clock = timeOptions{hour = Just Numeric, minute = Just TwoDigit}

berlin :: TimeOptions
berlin = clock{timeZone = "Europe/Berlin", hour12 = Just False}

afternoon :: UTCTime
afternoon = UTCTime (fromGregorian 2026 8 10) . secondsToDiffTime $ 12 * 3600 + 34 * 60 + 56

midwinter :: UTCTime
midwinter = UTCTime (fromGregorian 2026 1 10) . secondsToDiffTime $ 12 * 3600 + 34 * 60 + 56