Skip to content

Commit

Permalink
Merge pull request #22 from ChristophP/implement-custom-translations-…
Browse files Browse the repository at this point in the history
…constructors

Implement custom translations constructors
  • Loading branch information
ChristophP authored Jan 14, 2020
2 parents 3860284 + b8752ea commit fe7bdf7
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 39 deletions.
4 changes: 2 additions & 2 deletions elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "ChristophP/elm-i18next",
"summary": "A module to load, decode and use translations in your app",
"license": "BSD-3-Clause",
"version": "4.0.0",
"version": "4.1.0",
"exposed-modules": [
"I18Next"
],
Expand All @@ -15,4 +15,4 @@
"test-dependencies": {
"elm-explorations/test": "1.1.0 <= v < 2.0.0"
}
}
}
103 changes: 79 additions & 24 deletions src/I18Next.elm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module I18Next exposing
, translationsDecoder
, t, tr, tf, trf
, keys, hasKey
, Tree, fromTree, string, object
)

{-| This library provides a solution to load and display translations in your
Expand Down Expand Up @@ -40,12 +41,25 @@ translations.
@docs keys, hasKey
## Custom Building Translations
Most of the time you'll load your translations as JSON form a server, but there
may be times, when you want to build translations in your code. The following
functions let you build a `Translations` value programmatically.
@docs Tree, fromTree, string, object
-}

import Dict exposing (Dict)
import Json.Decode as Decode exposing (Decoder)


{-| A type representing a hierarchy of nested translations. You'll only ever
deal with this type directly, if you're using
[`string`](I18Next#string) and [`object`](I18Next#object).
-}
type Tree
= Branch (Dict String Tree)
| Leaf String
Expand Down Expand Up @@ -104,7 +118,7 @@ hasKey (Translations dict) key =


{-| Decode a JSON translations file. The JSON can be arbitrarly nested, but the
leaf values can only be strings. Use this decoder directly if you are passing
leaf values can only be strings. Use this decoder directly, if you are passing
the translations JSON into your elm app via flags or ports.
After decoding nested values will be available with any of the translate
functions separated with dots.
Expand Down Expand Up @@ -132,7 +146,8 @@ functions separated with dots.
-}
translationsDecoder : Decoder Translations
translationsDecoder =
Decode.map mapTreeToDict treeDecoder
Decode.dict treeDecoder
|> Decode.map (flattenTranslations >> Translations)


treeDecoder : Decoder Tree
Expand All @@ -144,8 +159,13 @@ treeDecoder =
]


foldTree : Dict String String -> Dict String Tree -> String -> Dict String String
foldTree initialValue dict namespace =
flattenTranslations : Dict String Tree -> Dict String String
flattenTranslations dict =
flattenTranslationsHelp Dict.empty "" dict


flattenTranslationsHelp : Dict String String -> String -> Dict String Tree -> Dict String String
flattenTranslationsHelp initialValue namespace dict =
Dict.foldl
(\key val acc ->
let
Expand All @@ -161,23 +181,12 @@ foldTree initialValue dict namespace =
Dict.insert (newNamespace key) str acc

Branch children ->
foldTree acc children (newNamespace key)
flattenTranslationsHelp acc (newNamespace key) children
)
initialValue
dict


mapTreeToDict : Tree -> Translations
mapTreeToDict tree =
case tree of
Branch dict ->
foldTree Dict.empty dict ""
|> Translations

_ ->
initialTranslations


{-| Translate a value at a given string.
{- If your translations are { "greet": { "hello": "Hello" } }
Expand All @@ -193,7 +202,7 @@ t (Translations translations) key =


replacePlaceholders : Replacements -> Delims -> String -> String
replacePlaceholders replacements delims string =
replacePlaceholders replacements delims str =
let
( start, end ) =
delimsToTuple delims
Expand All @@ -202,7 +211,7 @@ replacePlaceholders replacements delims string =
(\( key, value ) acc ->
String.replace (start ++ key ++ end) value acc
)
string
str
replacements


Expand Down Expand Up @@ -231,9 +240,12 @@ Use this when you need to replace placeholders.
-}
tr : Translations -> Delims -> String -> Replacements -> String
tr (Translations translations) delims key replacements =
Dict.get key translations
|> Maybe.map (replacePlaceholders replacements delims)
|> Maybe.withDefault key
case Dict.get key translations of
Just str ->
replacePlaceholders replacements delims str

Nothing ->
key


{-| Translate a value and try different fallback languages by providing a list
Expand Down Expand Up @@ -273,9 +285,52 @@ trf : List Translations -> Delims -> String -> Replacements -> String
trf translationsList delims key replacements =
case translationsList of
(Translations translations) :: rest ->
Dict.get key translations
|> Maybe.map (replacePlaceholders replacements delims)
|> Maybe.withDefault (trf rest delims key replacements)
case Dict.get key translations of
Just str ->
replacePlaceholders replacements delims str

Nothing ->
trf rest delims key replacements

[] ->
key


{-| Represents the leaf of a translations tree. It holds the actual translation
string.
-}
string : String -> Tree
string =
Leaf


{-| Let's you arange your translations in a hierarchy of objects.
-}
object : List ( String, Tree ) -> Tree
object =
Dict.fromList >> Branch


{-| Create a [`Translations`](I18Next#Translations) value from a list of pairs.
import I18Next exposing (string, object, fromTree, t)
translations =
fromTree
[ ("custom"
, object
[ ( "morning", string "Morning" )
, ( "evening", string "Evening" )
, ( "afternoon", string "Afternoon" )
]
)
, ("hello", string "hello")
]
-- use it like this
t translations "custom.morning" -- "Morning"
-}
fromTree : List ( String, Tree ) -> Translations
fromTree =
Dict.fromList >> flattenTranslations >> Translations
48 changes: 35 additions & 13 deletions tests/Tests.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Tests exposing (all)
module Tests exposing (..)

import Expect
import Fuzz
import I18Next
exposing
( Delims(..)
Expand Down Expand Up @@ -88,18 +89,6 @@ invalidReplacements =
]


all : Test
all =
describe "The I18Next Module"
[ decode
, translate
, translateWithPlaceholders
, translateWithFallback
, translateWithPlaceholdersAndFallback
, inspecting
]


decode : Test
decode =
describe "translationsDecoder"
Expand All @@ -117,6 +106,14 @@ decode =
Ok _ ->
Expect.fail "Decoding passed but should have failed."

Err err ->
Expect.pass
, test "fails when the JSON is a string and not an object" <|
\() ->
case Decode.decodeString translationsDecoder "\"String\"" of
Ok _ ->
Expect.fail "Decoding passed but should have failed."

Err err ->
Expect.pass
]
Expand Down Expand Up @@ -227,3 +224,28 @@ inspecting =
|> Expect.false "key should not be contained but is"
]
]


customTranslations : Test
customTranslations =
describe "custom translations"
[ fuzz Fuzz.string "can build working translations with a string" <|
\str ->
let
translations =
I18Next.fromTree [ ( "test", I18Next.string str ) ]
in
t translations "test" |> Expect.equal str
, fuzz Fuzz.string "can build working translations with an object" <|
\str ->
let
translations =
I18Next.fromTree
[ ( "obj"
, I18Next.object
[ ( "test", I18Next.string str ) ]
)
]
in
t translations "obj.test" |> Expect.equal str
]

0 comments on commit fe7bdf7

Please sign in to comment.