From 5042ad0217a403ee7e11b202f2917e1d8b67a015 Mon Sep 17 00:00:00 2001 From: Niklas Hofer Date: Mon, 23 Oct 2017 19:42:44 +0200 Subject: [PATCH 1/6] Make implementation easier to understand by introducing types describing their intent. --- src/CSV/Export.elm | 16 +++--- src/CSV/Import.elm | 89 ++++++++++++++++--------------- src/Localized.elm | 61 ++++++++++++++++++--- src/Localized/Parser.elm | 4 +- src/Localized/Parser/Internal.elm | 26 ++++----- src/Localized/Writer.elm | 28 +++++----- src/PO/Export.elm | 22 ++++---- src/PO/Import.elm | 6 +-- src/PO/Import/Internal.elm | 42 +++++++-------- 9 files changed, 172 insertions(+), 122 deletions(-) diff --git a/src/CSV/Export.elm b/src/CSV/Export.elm index f6a471d..819a957 100644 --- a/src/CSV/Export.elm +++ b/src/CSV/Export.elm @@ -7,7 +7,7 @@ module CSV.Export exposing (generate) -} import CSV.Template -import Localized +import Localized exposing (..) {-| Generate a CSV string from a list of localized elements (Localized.Element). @@ -18,7 +18,7 @@ Elm source code into a list of localized elements: |> CSV.Export.generate -} -generate : List Localized.Element -> String +generate : List Element -> String generate elements = List.map line elements |> List.map @@ -31,26 +31,26 @@ generate elements = |> flip String.append "\n" -line : Localized.Element -> List String +line : Element -> List String line element = case element of - Localized.ElementStatic static -> + ElementStatic static -> [ static.meta.moduleName, static.meta.key, static.meta.comment, "", static.value ] - Localized.ElementFormat format -> + ElementFormat format -> [ format.meta.moduleName, format.meta.key, format.meta.comment, String.join " " format.placeholders, formatString format.components ] -formatString : List Localized.FormatComponent -> String +formatString : List FormatComponent -> String formatString components = components |> List.map (\component -> case component of - Localized.FormatComponentStatic value -> + FormatComponentStatic value -> value - Localized.FormatComponentPlaceholder placeholder -> + FormatComponentPlaceholder placeholder -> CSV.Template.placeholder placeholder ) |> String.join "" diff --git a/src/CSV/Import.elm b/src/CSV/Import.elm index 8248046..e995005 100644 --- a/src/CSV/Import.elm +++ b/src/CSV/Import.elm @@ -15,7 +15,7 @@ elements. import Csv import Dict -import Localized +import Localized exposing (..) import Set @@ -29,7 +29,7 @@ You will usually use this output to create elm code: |> Localized.Writer.write -} -generate : String -> List ( String, List Localized.Element ) +generate : String -> List Module generate csv = case Csv.parse csv of Result.Ok lines -> @@ -40,7 +40,7 @@ generate csv = |> always [] -generateForCsv : Csv.Csv -> List ( String, List Localized.Element ) +generateForCsv : Csv.Csv -> List Module generateForCsv lines = let modules = @@ -60,21 +60,21 @@ generateForCsv lines = ) |> Dict.fromList in - -- Generate the source code for each module based on the lines - -- grouped in the expression above. - List.map - (\name -> - let - linesForThisModule = - Dict.get name linesForModules - |> Maybe.withDefault [] - in - ( name, generateForModule linesForThisModule ) - ) - modules - - -generateForModule : List (List String) -> List Localized.Element + -- Generate the source code for each module based on the lines + -- grouped in the expression above. + List.map + (\name -> + let + linesForThisModule = + Dict.get name linesForModules + |> Maybe.withDefault [] + in + ( name, generateForModule linesForThisModule ) + ) + modules + + +generateForModule : List (List String) -> List Element generateForModule lines = List.filterMap fromLine lines @@ -94,12 +94,12 @@ moduleNameForLine columns = Nothing -linesForModule : String -> List (List String) -> List (List String) +linesForModule : ModuleName -> List (List String) -> List (List String) linesForModule moduleName lines = List.filter (\line -> moduleNameForLine line == Just moduleName) lines -fromLine : List String -> Maybe Localized.Element +fromLine : List String -> Maybe Element fromLine columns = case columns of modulename :: key :: comment :: placeholders :: value :: xs -> @@ -109,7 +109,7 @@ fromLine columns = Nothing -code : String -> String -> String -> String -> String -> Localized.Element +code : ModuleName -> Key -> Comment -> String -> Value -> Element code modulename key comment placeholderString value = let placeholders = @@ -120,13 +120,13 @@ code modulename key comment placeholderString value = numPlaceholders = List.length placeholders in - if numPlaceholders == 0 then - staticElement modulename key comment value - else - formatElement modulename key comment placeholders value + if numPlaceholders == 0 then + staticElement modulename key comment value + else + formatElement modulename key comment placeholders value -formatElement : String -> String -> String -> List String -> String -> Localized.Element +formatElement : ModuleName -> Key -> Comment -> List Placeholder -> Value -> Element formatElement modulename key comment placeholders value = let components = @@ -140,32 +140,33 @@ formatElement modulename key comment placeholders value = String.split "}}" candidate |> withoutEmptyStrings -- ["p", " Goodbye "] -> [FormatComponentPlaceholder "p", FormatComponentStatic " Goodbye "] - |> List.indexedMap - (\index submatch -> - if index % 2 == 0 then - Localized.FormatComponentPlaceholder (String.trim submatch) - else - Localized.FormatComponentStatic submatch - ) + |> + List.indexedMap + (\index submatch -> + if index % 2 == 0 then + FormatComponentPlaceholder (String.trim submatch) + else + FormatComponentStatic submatch + ) else - [ Localized.FormatComponentStatic candidate ] + [ FormatComponentStatic candidate ] ) |> List.concat in - Localized.ElementFormat - { meta = - { moduleName = modulename - , key = key - , comment = comment + ElementFormat + { meta = + { moduleName = modulename + , key = key + , comment = comment + } + , placeholders = placeholders + , components = components } - , placeholders = placeholders - , components = components - } -staticElement : String -> String -> String -> String -> Localized.Element +staticElement : ModuleName -> Key -> Comment -> Value -> Element staticElement modulename key comment value = - Localized.ElementStatic + ElementStatic { meta = { moduleName = modulename , key = key diff --git a/src/Localized.elm b/src/Localized.elm index 85ed36d..dd96b47 100644 --- a/src/Localized.elm +++ b/src/Localized.elm @@ -5,13 +5,20 @@ module Localized , FormatComponent(..) , Meta , Static + , ModuleName + , Key + , Comment + , Value + , SourceCode + , Placeholder + , Module , isEmptyFormatComponent ) {-| This module provides data structures describing localized string functions and constants. -@docs Element, Meta, Static, Format, FormatComponent, isEmptyFormatComponent +@docs Element, Meta, Static, Format, FormatComponent, ModuleName, Key, Comment, Value, Placeholder, Module, SourceCode, isEmptyFormatComponent -} @@ -23,14 +30,50 @@ type Element | ElementFormat Format +{-| The name of an Elm module. +-} +type alias ModuleName = + String + + +{-| A Key. +-} +type alias Key = + String + + +{-| Elm code (snipped) that will be written to an .elm file. +-} +type alias SourceCode = + String + + +{-| String representation of a human readable comment. +-} +type alias Comment = + String + + +{-| A String holding the final value of a static translation. +-} +type alias Value = + String + + +{-| A Placeholder represents one argument given to the Translation functions +-} +type alias Placeholder = + String + + {-| Each localized element (static or format) has a key that is unique within a module. The comment should help translators and others understand how and where the localized element is used. -} type alias Meta = - { moduleName : String - , key : String - , comment : String + { moduleName : ModuleName + , key : Key + , comment : Comment } @@ -38,7 +81,7 @@ type alias Meta = It contains a single string value. -} type alias Static = - { meta : Meta, value : String } + { meta : Meta, value : Value } {-| A formatted string can contain placeholders and static components. This @@ -55,11 +98,17 @@ allows us to describe strings that contain dynamic values. -} type alias Format = { meta : Meta - , placeholders : List String + , placeholders : List Placeholder , components : List FormatComponent } +{-| The representation of an Elm module containing a list of Elements. +-} +type alias Module = + ( ModuleName, List Element ) + + {-| A list of components make up a formatted element. See Format. -} type FormatComponent diff --git a/src/Localized/Parser.elm b/src/Localized/Parser.elm index f9f9a6c..6900526 100644 --- a/src/Localized/Parser.elm +++ b/src/Localized/Parser.elm @@ -5,14 +5,14 @@ module Localized.Parser exposing (parse) @docs parse -} -import Localized +import Localized exposing (..) import Localized.Parser.Internal exposing (..) {-| Parses the source code of an elm module and returns a list of localized elements. -} -parse : String -> List Localized.Element +parse : SourceCode -> List Element parse source = let stringKeysAndParameters = diff --git a/src/Localized/Parser/Internal.elm b/src/Localized/Parser/Internal.elm index 9068de1..5655916 100644 --- a/src/Localized/Parser/Internal.elm +++ b/src/Localized/Parser/Internal.elm @@ -1,7 +1,7 @@ module Localized.Parser.Internal exposing (..) import List.Extra as List -import Localized +import Localized exposing (..) import Regex exposing (Regex) import Utils.Regex as Utils @@ -33,7 +33,7 @@ regexFormats key = |> Regex.regex -findModuleName : String -> String +findModuleName : SourceCode -> ModuleName findModuleName source = Regex.find (Regex.AtMost 1) regexFindModuleName source |> List.head @@ -44,7 +44,7 @@ findModuleName source = {-| Finds all top level string declarations, both constants (`key : String` and functions returning strings (e.g. `fun : String -> String`). -} -stringDeclarations : String -> List ( String, List String ) +stringDeclarations : SourceCode -> List ( Key, List String ) stringDeclarations source = Regex.find Regex.All regexStringDeclarations source |> List.filterMap @@ -65,7 +65,7 @@ stringDeclarations source = ) -findStaticElementForKey : String -> String -> String -> Maybe Localized.Element +findStaticElementForKey : ModuleName -> SourceCode -> Key -> Maybe Element findStaticElementForKey moduleName source key = let maybeValue = @@ -75,15 +75,15 @@ findStaticElementForKey moduleName source key = in case maybeValue of Just value -> - Localized.Static (Localized.Meta moduleName key (findComment source key)) value - |> Localized.ElementStatic + Static (Meta moduleName key (findComment source key)) value + |> ElementStatic |> Just Nothing -> Nothing -findFormatElementForKey : String -> String -> String -> Maybe Localized.Element +findFormatElementForKey : ModuleName -> SourceCode -> Key -> Maybe Element findFormatElementForKey moduleName source key = let regex = @@ -117,12 +117,12 @@ findFormatElementForKey moduleName source key = Nothing placeholderList -> - Localized.Format (Localized.Meta moduleName key (findComment source key)) placeholderList content - |> Localized.ElementFormat + Format (Meta moduleName key (findComment source key)) placeholderList content + |> ElementFormat |> Just -findComment : String -> String -> String +findComment : SourceCode -> Key -> Comment findComment source key = let match = @@ -133,15 +133,15 @@ findComment source key = |> Maybe.withDefault "" -formatComponentFromString : String -> Localized.FormatComponent +formatComponentFromString : String -> FormatComponent formatComponentFromString value = if String.endsWith "\"" value && String.startsWith "\"" value then -- Remove quotes from value String.dropLeft 1 value |> String.dropRight 1 - |> Localized.FormatComponentStatic + |> FormatComponentStatic else - Localized.FormatComponentPlaceholder value + FormatComponentPlaceholder value trimmedStrings : List String -> List String diff --git a/src/Localized/Writer.elm b/src/Localized/Writer.elm index bebfa28..060f5dc 100644 --- a/src/Localized/Writer.elm +++ b/src/Localized/Writer.elm @@ -7,13 +7,13 @@ elm modules implementing the localized elements. @docs generate -} -import Localized +import Localized exposing (..) {-| Generate elm-source code for a list of modules and their associated localized elements. -} -generate : List ( String, List Localized.Element ) -> List ( String, String ) +generate : List Module -> List ( ModuleName, SourceCode ) generate = List.map (\( modulename, elements ) -> @@ -21,7 +21,7 @@ generate = ) -moduleImplementation : String -> List Localized.Element -> String +moduleImplementation : ModuleName -> List Element -> SourceCode moduleImplementation name elements = "module " ++ name @@ -33,29 +33,29 @@ moduleImplementation name elements = ) -functionFromElement : Localized.Element -> String +functionFromElement : Element -> SourceCode functionFromElement element = case element of - Localized.ElementStatic static -> + ElementStatic static -> functionStatic static - Localized.ElementFormat format -> + ElementFormat format -> functionFormat format -tab : String +tab : SourceCode tab = " " -functionStatic : Localized.Static -> String +functionStatic : Static -> SourceCode functionStatic staticLocalized = comment staticLocalized.meta.comment ++ signature staticLocalized.meta.key [] ++ ("\n" ++ tab ++ toString staticLocalized.value) -functionFormat : Localized.Format -> String +functionFormat : Format -> SourceCode functionFormat format = comment format.meta.comment ++ signature format.meta.key format.placeholders @@ -65,7 +65,7 @@ functionFormat format = ) -formatComponentsImplementation : Int -> Localized.FormatComponent -> String +formatComponentsImplementation : Int -> FormatComponent -> SourceCode formatComponentsImplementation index component = let prefix = @@ -75,14 +75,14 @@ formatComponentsImplementation index component = tab ++ tab ++ "++ " in case component of - Localized.FormatComponentStatic string -> + FormatComponentStatic string -> prefix ++ toString string - Localized.FormatComponentPlaceholder string -> + FormatComponentPlaceholder string -> prefix ++ String.trim string -signature : String -> List String -> String +signature : Key -> List Placeholder -> SourceCode signature key placeholders = let num = @@ -104,7 +104,7 @@ signature key placeholders = ++ (key ++ parameters ++ " =") -comment : String -> String +comment : Comment -> SourceCode comment string = if String.isEmpty string then "" diff --git a/src/PO/Export.elm b/src/PO/Export.elm index b4807cf..b7877f1 100644 --- a/src/PO/Export.elm +++ b/src/PO/Export.elm @@ -7,7 +7,7 @@ https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.htm @docs generate -} -import Localized +import Localized exposing (..) import PO.Template @@ -19,24 +19,24 @@ Elm source code into a list of localized elements: |> PO.Export.generate -} -generate : List Localized.Element -> String +generate : List Element -> String generate elements = List.map line elements |> String.join "\n\n" |> flip String.append "\n" -line : Localized.Element -> String +line : Element -> String line element = case element of - Localized.ElementStatic static -> + ElementStatic static -> commentLine static.meta.comment ++ "\n" ++ identifier static.meta.moduleName static.meta.key ++ "\n" ++ staticElement static.value - Localized.ElementFormat format -> + ElementFormat format -> commentLine format.meta.comment ++ "\n" ++ commentLine (PO.Template.placeholderCommentPrefix ++ String.join " " format.placeholders) @@ -46,7 +46,7 @@ line element = ++ ("msgstr " ++ formatElement format.components) -commentLine : String -> String +commentLine : Comment -> String commentLine comment = String.split "\n" comment |> String.join "\n#. " @@ -54,26 +54,26 @@ commentLine comment = |> String.trim -identifier : String -> String -> String +identifier : ModuleName -> Key -> String identifier modulename key = "msgid \"" ++ modulename ++ "." ++ key ++ "\"" -staticElement : String -> String +staticElement : Value -> String staticElement value = "msgstr " ++ toString value -formatElement : List Localized.FormatComponent -> String +formatElement : List FormatComponent -> String formatElement list = list |> List.map (\element -> case element of - Localized.FormatComponentPlaceholder placeholder -> + FormatComponentPlaceholder placeholder -> PO.Template.placeholder placeholder - Localized.FormatComponentStatic string -> + FormatComponentStatic string -> string ) |> String.join "" diff --git a/src/PO/Import.elm b/src/PO/Import.elm index 49aa798..d445ce0 100644 --- a/src/PO/Import.elm +++ b/src/PO/Import.elm @@ -12,7 +12,7 @@ elements. -} import Dict exposing (Dict) -import Localized exposing (FormatComponent) +import Localized exposing (..) import PO.Import.Internal exposing (..) @@ -27,7 +27,7 @@ You will usually use this output to create elm code: |> Localized.Writer.write -} -generate : String -> List ( String, List Localized.Element ) +generate : String -> List Module generate poString = let keysInModules = @@ -41,7 +41,7 @@ generate poString = keysInModules -generateModule : String -> String -> List String -> List Localized.Element +generateModule : String -> ModuleName -> List Key -> List Element generateModule poString moduleName allKeys = let fullComments = diff --git a/src/PO/Import/Internal.elm b/src/PO/Import/Internal.elm index 5424596..f2fab47 100644 --- a/src/PO/Import/Internal.elm +++ b/src/PO/Import/Internal.elm @@ -9,7 +9,7 @@ module PO.Import.Internal ) import Dict exposing (Dict) -import Localized exposing (FormatComponent) +import Localized exposing (..) import Regex exposing (Regex) import Set import String.Extra as String @@ -17,7 +17,7 @@ import Utils.Regex import PO.Template -element : String -> String -> String -> String -> Localized.Element +element : ModuleName -> Key -> Value -> Comment -> Element element moduleName key value fullComment = let comment = @@ -49,12 +49,12 @@ element moduleName key value fullComment = } in if List.isEmpty placeholders then - Localized.ElementStatic + ElementStatic { meta = meta , value = unquotedValue } else - Localized.ElementFormat + ElementFormat { meta = meta , placeholders = placeholders , components = formatComponentsFromValue unquotedValue placeholders @@ -65,7 +65,7 @@ element moduleName key value fullComment = ---- KEYS -fullKey : String -> String -> String +fullKey : ModuleName -> Key -> String fullKey moduleName key = moduleName ++ "." ++ key @@ -73,7 +73,7 @@ fullKey moduleName key = {-| Extract a list of all localization keys per module from a PO string file's contents. -} -keys : String -> List ( String, List String ) +keys : String -> List ( ModuleName, List String ) keys poString = let matches = @@ -119,7 +119,7 @@ keys poString = file, for all given keys in a module. The dict will contain use the localized key for key and the value will be the multiline comment. -} -poComments : String -> String -> List String -> Dict String String +poComments : String -> ModuleName -> List Key -> Dict Key String poComments poString moduleName allKeys = allKeys |> List.map @@ -133,7 +133,7 @@ poComments poString moduleName allKeys = |> Dict.fromList -commentFromPoComment : String -> String +commentFromPoComment : String -> Comment commentFromPoComment poComment = String.trim poComment |> String.split "#." @@ -154,7 +154,7 @@ following format: #. i18n: placeholders: placeh1, placeh2 -} -placeholdersFromPoComment : String -> List String +placeholdersFromPoComment : String -> List Placeholder placeholdersFromPoComment poComment = let placeholdersPrefix = @@ -184,7 +184,7 @@ placeholdersFromPoComment poComment = {-| Extract all values for a module and a given list of keys from a PO file. The dict will reference the value by its localization key. -} -values : String -> String -> List String -> Dict String String +values : String -> ModuleName -> List Key -> Dict Key Value values poString moduleName allKeys = allKeys |> List.map @@ -203,18 +203,18 @@ values poString moduleName allKeys = placeholder definition is missing form the comment, but ma lead to issues with sortine. For this reason, we only use this as a fallback an log an error. -} -placeholdersInValue : String -> List String +placeholdersInValue : Value -> List Placeholder placeholdersInValue value = Regex.find Regex.All regexForPlaceholder value |> List.filterMap (\match -> Utils.Regex.submatchAt 0 (Just match)) -formatComponentsFromValue : String -> List String -> List Localized.FormatComponent +formatComponentsFromValue : Value -> List Placeholder -> List FormatComponent formatComponentsFromValue value placeholders = - findPlaceholdersInStaticComponents [ Localized.FormatComponentStatic value ] placeholders + findPlaceholdersInStaticComponents [ FormatComponentStatic value ] placeholders -findPlaceholdersInStaticComponents : List Localized.FormatComponent -> List String -> List Localized.FormatComponent +findPlaceholdersInStaticComponents : List FormatComponent -> List Placeholder -> List FormatComponent findPlaceholdersInStaticComponents components placeholders = case List.head placeholders of Nothing -> @@ -226,16 +226,16 @@ findPlaceholdersInStaticComponents components placeholders = List.map (\component -> case component of - Localized.FormatComponentPlaceholder _ -> + FormatComponentPlaceholder _ -> [ component ] - Localized.FormatComponentStatic value -> + FormatComponentStatic value -> let subComponents = String.split (PO.Template.placeholder nextPlaceholder) value - |> List.map Localized.FormatComponentStatic - |> List.intersperse (Localized.FormatComponentPlaceholder nextPlaceholder) - |> List.filter (Localized.isEmptyFormatComponent >> not) + |> List.map FormatComponentStatic + |> List.intersperse (FormatComponentPlaceholder nextPlaceholder) + |> List.filter (isEmptyFormatComponent >> not) in subComponents ) @@ -249,13 +249,13 @@ findPlaceholdersInStaticComponents components placeholders = ---- REGEX -regexComments : String -> Regex +regexComments : Key -> Regex regexComments key = -- Find all lines preceeding `msgid "key"` that start with `#.` Regex.regex ("((?:#\\.[^\\n]*\\n)*)msgid " ++ toString key) -regexForValue : String -> Regex +regexForValue : Key -> Regex regexForValue key = -- Find all lines succeeding `msgid "key" \nmsgstr` until the two successive white lines Regex.regex From 71a6a464cc378b0e929a6bf7c937d713d45feb21 Mon Sep 17 00:00:00 2001 From: Niklas Hofer Date: Tue, 24 Oct 2017 16:21:59 +0200 Subject: [PATCH 2/6] Introduce run-time language switching This incorporates all present translations into one build. Instead of a symlink to the compile-time language, a switch is generated for every translation module. This switch contains all the functions of the original(s), but they take a Language as an extra (preceeding) argument, delegating the translation itself to the corresponding module for that language. Migration of apps is needed, instructions in README.md. NOTE: This breaks the currently implemented behaviour of "one language per build". --- .gitignore | 1 - Makefile | 6 + README.md | 69 ++ dist/elm.js | 693 ++++++++++++++---- .../Main.elm => src/Translation/Main/De.elm} | 3 +- .../Main.elm => src/Translation/Main/En.elm} | 6 +- extractor.js | 44 +- src/Localized.elm | 70 +- src/Localized/Switch.elm | 161 ++++ src/Localized/Writer.elm | 99 +-- src/Localized/Writer/Element.elm | 74 ++ src/Localized/Writer/Module.elm | 42 ++ src/Main.elm | 53 +- 13 files changed, 1086 insertions(+), 235 deletions(-) rename example/{Translation/De/Main.elm => src/Translation/Main/De.elm} (85%) rename example/{Translation/En/Main.elm => src/Translation/Main/En.elm} (54%) create mode 100644 src/Localized/Switch.elm create mode 100644 src/Localized/Writer/Element.elm create mode 100644 src/Localized/Writer/Module.elm diff --git a/.gitignore b/.gitignore index fd7400f..40c9404 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ elm-stuff node_modules out -example/src/Translation linter-logs build diff --git a/Makefile b/Makefile index ca8b70d..a52a9ba 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,12 @@ dist: $(ELM_FILES) elm-make src/Main.elm --output dist/elm.js +gentest: dist + cd example && node ../extractor.js -l De,En -s --root src + +imptest: dist + cd example && node ../extractor.js --format CSV --language En --importOutput src/ --import languages/en.csv + test: ## Run tests ./node_modules/elm-test/bin/elm-test diff --git a/README.md b/README.md index a400956..2c4a390 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,75 @@ elm-i18n-generator --format PO --language De --import export.po Results in the same `import/De/Translation/Main.elm` as in the [CSV example](#import-generate-elm-source-code-from-csv). + +#### Generate Elm source for switching Language during run-time + +Pass all the languages you want to switch between as a comma-separated list. + +```bash +elm-i18n-generator --language De,Pl,En,Fr --genswitch +``` + +#### Migrate legacy symlinked translations to switchables + +Remove the legacy symlink +``` +rm src/Translation +``` + +Export each of your languages to CSV temporarily +``` +mkdir languages +elm-i18n.generator --format CSV --language En --root Translation/ --export --exportOutput languages/en.csv +``` + +Then, import them again. +``` +elm-i18n-generator --format CSV --language En --importOutput src --import languages/en.csv +``` + + +Finally, generate the switch +``` +elm-i18n-generator --language De,En --genswitch --importOutput . --root src +``` + +After this your Project should look like this: + +``` +project +└── src/ + ├── Main.elm (e.g. imports Translation.Main) + ├── View.elm (e.g. imports Translation.View) + └── Translation + ├── Main.elm (module Translation.Main) + ├── View.elm (module Translation.View) + ├── Main/ + │ ├── De.elm (module Translation.Main.De) + │ └── En.elm (module Translation.View.En) + └── View/ + ├── De.elm (module Translation.Main.De) + └── En.elm (module Translation.View.En) +``` + +Use the `Language` type to dynamically translate your websites: + + +``` +import Translation exposing (Language(..)) + +state = + { language = Language + } + +render state = + div [] + [ text (Translation.Main.greetingWithName state.language "Leonard") + , text "and in German:" + , text (Translation.Main.greetingWithName De "Leonard") + ] +``` + ## Advantages + Each build of your app only contains one language. diff --git a/dist/elm.js b/dist/elm.js index 330a6e3..0f94abc 100644 --- a/dist/elm.js +++ b/dist/elm.js @@ -9190,12 +9190,35 @@ var _iosphere$elm_i18n$CSV_Template$placeholder = function (placeholder) { }; var _iosphere$elm_i18n$CSV_Template$headers = 'Module,Key,Comment,Supported Placeholders,Translation'; +var _iosphere$elm_i18n$Localized$namedModule = function (name) { + return { + ctor: '_Tuple2', + _0: name, + _1: {ctor: '[]'} + }; +}; +var _iosphere$elm_i18n$Localized$languageModuleName = F2( + function (name, lang) { + return A2( + _elm_lang$core$Basics_ops['++'], + name, + A2(_elm_lang$core$Basics_ops['++'], '.', lang)); + }); +var _iosphere$elm_i18n$Localized$elementMeta = F2( + function (accessor, element) { + var _p0 = element; + if (_p0.ctor === 'ElementStatic') { + return accessor(_p0._0.meta); + } else { + return accessor(_p0._0.meta); + } + }); var _iosphere$elm_i18n$Localized$isEmptyFormatComponent = function (comp) { - var _p0 = comp; - if (_p0.ctor === 'FormatComponentStatic') { - return _elm_lang$core$String$isEmpty(_p0._0); + var _p1 = comp; + if (_p1.ctor === 'FormatComponentStatic') { + return _elm_lang$core$String$isEmpty(_p1._0); } else { - return _elm_lang$core$String$isEmpty(_p0._0); + return _elm_lang$core$String$isEmpty(_p1._0); } }; var _iosphere$elm_i18n$Localized$Meta = F3( @@ -9216,6 +9239,48 @@ var _iosphere$elm_i18n$Localized$ElementFormat = function (a) { var _iosphere$elm_i18n$Localized$ElementStatic = function (a) { return {ctor: 'ElementStatic', _0: a}; }; +var _iosphere$elm_i18n$Localized$elementRemoveLang = F2( + function (lang, element) { + var changeName = F2( + function (meta, name) { + return _elm_lang$core$Native_Utils.update( + meta, + {moduleName: name}); + }); + var moduleName = A2( + _iosphere$elm_i18n$Localized$elementMeta, + function (_) { + return _.moduleName; + }, + element); + var cleanedName = A2( + _elm_lang$core$String$join, + '.', + A2( + _elm_lang$core$List$filter, + function (p) { + return !_elm_lang$core$Native_Utils.eq(p, lang); + }, + A2(_elm_lang$core$String$split, '.', moduleName))); + var _p2 = element; + if (_p2.ctor === 'ElementStatic') { + var _p3 = _p2._0; + return _iosphere$elm_i18n$Localized$ElementStatic( + _elm_lang$core$Native_Utils.update( + _p3, + { + meta: A2(changeName, _p3.meta, cleanedName) + })); + } else { + var _p4 = _p2._0; + return _iosphere$elm_i18n$Localized$ElementFormat( + _elm_lang$core$Native_Utils.update( + _p4, + { + meta: A2(changeName, _p4.meta, cleanedName) + })); + } + }); var _iosphere$elm_i18n$Localized$FormatComponentPlaceholder = function (a) { return {ctor: 'FormatComponentPlaceholder', _0: a}; }; @@ -9789,62 +9854,55 @@ var _iosphere$elm_i18n$Localized_Parser$parse = function (source) { stringKeysAndParameters); }; -var _iosphere$elm_i18n$Localized_Writer$comment = function (string) { - return _elm_lang$core$String$isEmpty(string) ? '' : A2( +var _iosphere$elm_i18n$Localized_Writer_Module$importModuleExposingAll = function (_p0) { + var _p1 = _p0; + return A2( _elm_lang$core$Basics_ops['++'], - '{-| ', - A2(_elm_lang$core$Basics_ops['++'], string, '\n-}\n')); + 'import ', + A2(_elm_lang$core$Basics_ops['++'], _p1._0, ' exposing (..)\n')); }; -var _iosphere$elm_i18n$Localized_Writer$signature = F2( - function (key, placeholders) { - var num = _elm_lang$core$List$length(placeholders); - var types = _elm_lang$core$Native_Utils.eq(num, 0) ? 'String' : A2( - _elm_lang$core$String$join, - ' -> ', - A2(_elm_lang$core$List$repeat, num + 1, 'String')); - var parameters = _elm_lang$core$Native_Utils.eq(num, 0) ? '' : A2( - _elm_lang$core$Basics_ops['++'], - ' ', - A2(_elm_lang$core$String$join, ' ', placeholders)); - return A2( - _elm_lang$core$Basics_ops['++'], - A2( - _elm_lang$core$Basics_ops['++'], - key, - A2( - _elm_lang$core$Basics_ops['++'], - ' : ', - A2(_elm_lang$core$Basics_ops['++'], types, '\n'))), - A2( - _elm_lang$core$Basics_ops['++'], - key, - A2(_elm_lang$core$Basics_ops['++'], parameters, ' ='))); - }); -var _iosphere$elm_i18n$Localized_Writer$tab = ' '; -var _iosphere$elm_i18n$Localized_Writer$functionStatic = function (staticLocalized) { +var _iosphere$elm_i18n$Localized_Writer_Module$importModule = function (_p2) { + var _p3 = _p2; return A2( _elm_lang$core$Basics_ops['++'], - _iosphere$elm_i18n$Localized_Writer$comment(staticLocalized.meta.comment), - A2( - _elm_lang$core$Basics_ops['++'], - A2( - _iosphere$elm_i18n$Localized_Writer$signature, - staticLocalized.meta.key, - {ctor: '[]'}), - A2( - _elm_lang$core$Basics_ops['++'], - '\n', - A2( - _elm_lang$core$Basics_ops['++'], - _iosphere$elm_i18n$Localized_Writer$tab, - _elm_lang$core$Basics$toString(staticLocalized.value))))); + 'import ', + A2(_elm_lang$core$Basics_ops['++'], _p3._0, '\n')); }; -var _iosphere$elm_i18n$Localized_Writer$formatComponentsImplementation = F2( +var _iosphere$elm_i18n$Localized_Writer_Module$head = function (_p4) { + var _p5 = _p4; + return A2( + _elm_lang$core$Basics_ops['++'], + 'module ', + A2(_elm_lang$core$Basics_ops['++'], _p5._0, ' exposing (..)\n\n{-| -}\n\n')); +}; +var _iosphere$elm_i18n$Localized_Writer_Module$elements = F2( + function (functionImplementation, _p6) { + var _p7 = _p6; + return A3( + _elm_lang$core$Basics$flip, + _elm_lang$core$String$append, + '\n', + _elm_lang$core$String$trim( + A2( + _elm_lang$core$String$join, + '\n\n\n', + A2(_elm_lang$core$List$map, functionImplementation, _p7._1)))); + }); +var _iosphere$elm_i18n$Localized_Writer_Module$implementation = F2( + function (functionImplementation, mod) { + return A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer_Module$head(mod), + A2(_iosphere$elm_i18n$Localized_Writer_Module$elements, functionImplementation, mod)); + }); + +var _iosphere$elm_i18n$Localized_Writer_Element$tab = ' '; +var _iosphere$elm_i18n$Localized_Writer_Element$formatComponentsImplementation = F2( function (index, component) { - var prefix = _elm_lang$core$Native_Utils.eq(index, 0) ? _iosphere$elm_i18n$Localized_Writer$tab : A2( + var prefix = _elm_lang$core$Native_Utils.eq(index, 0) ? _iosphere$elm_i18n$Localized_Writer_Element$tab : A2( _elm_lang$core$Basics_ops['++'], - _iosphere$elm_i18n$Localized_Writer$tab, - A2(_elm_lang$core$Basics_ops['++'], _iosphere$elm_i18n$Localized_Writer$tab, '++ ')); + _iosphere$elm_i18n$Localized_Writer_Element$tab, + A2(_elm_lang$core$Basics_ops['++'], _iosphere$elm_i18n$Localized_Writer_Element$tab, '++ ')); var _p0 = component; if (_p0.ctor === 'FormatComponentStatic') { return A2( @@ -9858,61 +9916,364 @@ var _iosphere$elm_i18n$Localized_Writer$formatComponentsImplementation = F2( _elm_lang$core$String$trim(_p0._0)); } }); -var _iosphere$elm_i18n$Localized_Writer$functionFormat = function (format) { +var _iosphere$elm_i18n$Localized_Writer_Element$head = function (element) { return A2( _elm_lang$core$Basics_ops['++'], - _iosphere$elm_i18n$Localized_Writer$comment(format.meta.comment), + A2( + _iosphere$elm_i18n$Localized$elementMeta, + function (_) { + return _.key; + }, + element), A2( _elm_lang$core$Basics_ops['++'], - A2(_iosphere$elm_i18n$Localized_Writer$signature, format.meta.key, format.placeholders), - A2( - _elm_lang$core$Basics_ops['++'], - '\n', - A2( - _elm_lang$core$String$join, - '\n', - A2(_elm_lang$core$List$indexedMap, _iosphere$elm_i18n$Localized_Writer$formatComponentsImplementation, format.components))))); + function () { + var _p1 = element; + if (_p1.ctor === 'ElementStatic') { + return ''; + } else { + return A2( + _elm_lang$core$Basics_ops['++'], + ' ', + A2(_elm_lang$core$String$join, '', _p1._0.placeholders)); + } + }(), + ' =')); }; -var _iosphere$elm_i18n$Localized_Writer$functionFromElement = function (element) { - var _p1 = element; - if (_p1.ctor === 'ElementStatic') { - return _iosphere$elm_i18n$Localized_Writer$functionStatic(_p1._0); +var _iosphere$elm_i18n$Localized_Writer_Element$body = function (element) { + var _p2 = element; + if (_p2.ctor === 'ElementStatic') { + return A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer_Element$tab, + _elm_lang$core$Basics$toString(_p2._0.value)); } else { - return _iosphere$elm_i18n$Localized_Writer$functionFormat(_p1._0); + return A2( + _elm_lang$core$String$join, + '\n', + A2(_elm_lang$core$List$indexedMap, _iosphere$elm_i18n$Localized_Writer_Element$formatComponentsImplementation, _p2._0.components)); } }; -var _iosphere$elm_i18n$Localized_Writer$moduleImplementation = F2( - function (name, elements) { +var _iosphere$elm_i18n$Localized_Writer_Element$placeholders = function (element) { + var _p3 = element; + if (_p3.ctor === 'ElementStatic') { + return 'String'; + } else { + var num = _elm_lang$core$List$length(_p3._0.placeholders); return A2( + _elm_lang$core$String$join, + ' -> ', + A2(_elm_lang$core$List$repeat, num + 1, 'String')); + } +}; +var _iosphere$elm_i18n$Localized_Writer_Element$typeDeclaration = function (element) { + return A2( + _elm_lang$core$Basics_ops['++'], + A2( + _iosphere$elm_i18n$Localized$elementMeta, + function (_) { + return _.key; + }, + element), + A2( + _elm_lang$core$Basics_ops['++'], + ' : ', + _iosphere$elm_i18n$Localized_Writer_Element$placeholders(element))); +}; + +var _iosphere$elm_i18n$Localized_Switch$removeLocale = F2( + function (langs, element) { + return A3(_elm_lang$core$List$foldr, _iosphere$elm_i18n$Localized$elementRemoveLang, element, langs); + }); +var _iosphere$elm_i18n$Localized_Switch$mainModule = function (languages) { + var name = 'Translation'; + var mod = { + ctor: '_Tuple2', + _0: name, + _1: {ctor: '[]'} + }; + return { + ctor: '_Tuple2', + _0: name, + _1: A2( _elm_lang$core$Basics_ops['++'], - 'module ', + _iosphere$elm_i18n$Localized_Writer_Module$head(mod), A2( _elm_lang$core$Basics_ops['++'], - name, + 'type Language = ', A2( _elm_lang$core$Basics_ops['++'], - ' exposing (..)\n\n{-| -}\n\n\n', - A3( - _elm_lang$core$Basics$flip, - _elm_lang$core$String$append, + A2(_elm_lang$core$String$join, ' | ', languages), + '\n'))) + }; +}; +var _iosphere$elm_i18n$Localized_Switch$elementSource = F2( + function (languages, element) { + var placeholders = _iosphere$elm_i18n$Localized_Writer_Element$placeholders(element); + var moduleName = A2( + _iosphere$elm_i18n$Localized$elementMeta, + function (_) { + return _.moduleName; + }, + element); + var name = A2( + _iosphere$elm_i18n$Localized$elementMeta, + function (_) { + return _.key; + }, + element); + return A2( + _elm_lang$core$Basics_ops['++'], + name, + A2( + _elm_lang$core$Basics_ops['++'], + ' : Language -> ', + A2( + _elm_lang$core$Basics_ops['++'], + placeholders, + A2( + _elm_lang$core$Basics_ops['++'], '\n', - _elm_lang$core$String$trim( + A2( + _elm_lang$core$Basics_ops['++'], + name, A2( - _elm_lang$core$String$join, - '\n\n\n', - A2(_elm_lang$core$List$map, _iosphere$elm_i18n$Localized_Writer$functionFromElement, elements))))))); - }); -var _iosphere$elm_i18n$Localized_Writer$generate = _elm_lang$core$List$map( - function (_p2) { - var _p3 = _p2; - var _p4 = _p3._0; + _elm_lang$core$Basics_ops['++'], + ' language =\n', + A2( + _elm_lang$core$Basics_ops['++'], + A2(_elm_lang$core$Basics_ops['++'], _iosphere$elm_i18n$Localized_Writer_Element$tab, 'case language of\n'), + A2( + _elm_lang$core$String$join, + '\n', + A2( + _elm_lang$core$List$map, + function (l) { + return A2( + _elm_lang$core$Basics_ops['++'], + A2(_elm_lang$core$Basics_ops['++'], _iosphere$elm_i18n$Localized_Writer_Element$tab, _iosphere$elm_i18n$Localized_Writer_Element$tab), + A2( + _elm_lang$core$Basics_ops['++'], + l, + A2( + _elm_lang$core$Basics_ops['++'], + ' -> ', + A2( + _elm_lang$core$Basics_ops['++'], + moduleName, + A2( + _elm_lang$core$Basics_ops['++'], + '.', + A2( + _elm_lang$core$Basics_ops['++'], + l, + A2(_elm_lang$core$Basics_ops['++'], '.', name))))))); + }, + languages))))))))); + }); +var _iosphere$elm_i18n$Localized_Switch$switchSource = F2( + function (languages, mod) { + var _p0 = mod; + var moduleName = _p0._0; return { ctor: '_Tuple2', - _0: _p4, - _1: A2(_iosphere$elm_i18n$Localized_Writer$moduleImplementation, _p4, _p3._1) + _0: moduleName, + _1: A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer_Module$head(mod), + A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer_Module$importModuleExposingAll( + { + ctor: '_Tuple2', + _0: 'Translation', + _1: {ctor: '[]'} + }), + A2( + _elm_lang$core$Basics_ops['++'], + A2( + _elm_lang$core$String$join, + '', + A2( + _elm_lang$core$List$map, + function (_p1) { + return _iosphere$elm_i18n$Localized_Writer_Module$importModule( + _iosphere$elm_i18n$Localized$namedModule( + A2(_iosphere$elm_i18n$Localized$languageModuleName, moduleName, _p1))); + }, + languages)), + A2( + _elm_lang$core$Basics_ops['++'], + '\n\n', + A2( + _iosphere$elm_i18n$Localized_Writer_Module$elements, + _iosphere$elm_i18n$Localized_Switch$elementSource(languages), + mod))))) + }; + }); +var _iosphere$elm_i18n$Localized_Switch$indexBy = F2( + function (keymaker, elements) { + return A3( + _elm_lang$core$List$foldr, + F2( + function (e, d) { + return A3( + _elm_lang$core$Dict$update, + keymaker(e), + function (v) { + var _p2 = v; + if (_p2.ctor === 'Nothing') { + return _elm_lang$core$Maybe$Just( + { + ctor: '::', + _0: e, + _1: {ctor: '[]'} + }); + } else { + return _elm_lang$core$Maybe$Just( + {ctor: '::', _0: e, _1: _p2._0}); + } + }, + d); + }), + _elm_lang$core$Dict$empty, + elements); + }); +var _iosphere$elm_i18n$Localized_Switch$member = F2( + function (e, list) { + var sameElement = F2( + function (e1, e2) { + var _p3 = {ctor: '_Tuple2', _0: e1, _1: e2}; + if (_p3._0.ctor === 'ElementStatic') { + if (_p3._1.ctor === 'ElementFormat') { + return false; + } else { + var _p5 = _p3._1._0; + var _p4 = _p3._0._0; + return _elm_lang$core$Native_Utils.eq(_p4.meta.moduleName, _p5.meta.moduleName) && _elm_lang$core$Native_Utils.eq(_p4.meta.key, _p5.meta.key); + } + } else { + if (_p3._1.ctor === 'ElementStatic') { + return false; + } else { + var _p7 = _p3._1._0; + var _p6 = _p3._0._0; + return _elm_lang$core$Native_Utils.eq(_p6.meta.moduleName, _p7.meta.moduleName) && _elm_lang$core$Native_Utils.eq(_p6.meta.key, _p7.meta.key); + } + } + }); + return A2( + _elm_lang$core$List$any, + sameElement(e), + list); + }); +var _iosphere$elm_i18n$Localized_Switch$flatten2D = function (list) { + return A3( + _elm_lang$core$List$foldr, + F2( + function (x, y) { + return A2(_elm_lang$core$Basics_ops['++'], x, y); + }), + {ctor: '[]'}, + list); +}; +var _iosphere$elm_i18n$Localized_Switch$u = F2( + function (list, have) { + u: + while (true) { + var _p8 = list; + if (_p8.ctor === '::') { + var _p10 = _p8._1; + var _p9 = _p8._0; + if (A2(_iosphere$elm_i18n$Localized_Switch$member, _p9, have)) { + var _v3 = _p10, + _v4 = have; + list = _v3; + have = _v4; + continue u; + } else { + var _v5 = _p10, + _v6 = {ctor: '::', _0: _p9, _1: have}; + list = _v5; + have = _v6; + continue u; + } + } else { + return have; + } + } + }); +var _iosphere$elm_i18n$Localized_Switch$unique = function (elements) { + return A2( + _iosphere$elm_i18n$Localized_Switch$u, + elements, + {ctor: '[]'}); +}; +var _iosphere$elm_i18n$Localized_Switch$generate = F2( + function (languages, sources) { + return { + ctor: '::', + _0: _iosphere$elm_i18n$Localized_Switch$mainModule(languages), + _1: A2( + _elm_lang$core$List$map, + _iosphere$elm_i18n$Localized_Switch$switchSource(languages), + _elm_lang$core$Dict$toList( + A2( + _iosphere$elm_i18n$Localized_Switch$indexBy, + _iosphere$elm_i18n$Localized$elementMeta( + function (_) { + return _.moduleName; + }), + _iosphere$elm_i18n$Localized_Switch$unique( + A2( + _elm_lang$core$List$map, + _iosphere$elm_i18n$Localized_Switch$removeLocale(languages), + _iosphere$elm_i18n$Localized_Switch$flatten2D( + A2(_elm_lang$core$List$map, _iosphere$elm_i18n$Localized_Parser$parse, sources))))))) }; }); +var _iosphere$elm_i18n$Localized_Writer$comment = function (string) { + return _elm_lang$core$String$isEmpty(string) ? '' : A2( + _elm_lang$core$Basics_ops['++'], + '{-| ', + A2(_elm_lang$core$Basics_ops['++'], string, '\n-}\n')); +}; +var _iosphere$elm_i18n$Localized_Writer$element = function (element) { + var c = A2( + _iosphere$elm_i18n$Localized$elementMeta, + function (_) { + return _.comment; + }, + element); + return A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer$comment(c), + A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer_Element$typeDeclaration(element), + A2( + _elm_lang$core$Basics_ops['++'], + '\n', + A2( + _elm_lang$core$Basics_ops['++'], + _iosphere$elm_i18n$Localized_Writer_Element$head(element), + A2( + _elm_lang$core$Basics_ops['++'], + '\n', + _iosphere$elm_i18n$Localized_Writer_Element$body(element)))))); +}; +var _iosphere$elm_i18n$Localized_Writer$moduleImplementation = function (mod) { + var _p0 = mod; + var moduleName = _p0._0; + return { + ctor: '_Tuple2', + _0: moduleName, + _1: A2(_iosphere$elm_i18n$Localized_Writer_Module$implementation, _iosphere$elm_i18n$Localized_Writer$element, mod) + }; +}; +var _iosphere$elm_i18n$Localized_Writer$generate = _elm_lang$core$List$map(_iosphere$elm_i18n$Localized_Writer$moduleImplementation); + var _iosphere$elm_i18n$PO_Template$placeholderCommentPrefix = 'i18n: placeholders: '; var _iosphere$elm_i18n$PO_Template$placeholder = function (placeholder) { return A2( @@ -10336,8 +10697,19 @@ var _iosphere$elm_i18n$PO_Import$generate = function (poString) { keysInModules); }; +var _iosphere$elm_i18n$Main$addLanguageToModuleName = function (lang) { + return _elm_lang$core$Tuple$mapFirst( + A2(_elm_lang$core$Basics$flip, _iosphere$elm_i18n$Localized$languageModuleName, lang)); +}; +var _iosphere$elm_i18n$Main$slashifyModuleName = _elm_lang$core$Tuple$mapFirst( + function (_p0) { + return A2( + _elm_lang$core$String$join, + '/', + A2(_elm_lang$core$String$split, '.', _p0)); + }); var _iosphere$elm_i18n$Main$update = F2( - function (_p0, model) { + function (_p1, model) { return {ctor: '_Tuple2', _0: model, _1: _elm_lang$core$Platform_Cmd$none}; }); var _iosphere$elm_i18n$Main$exportResult = _elm_lang$core$Native_Platform.outgoingPort( @@ -10348,8 +10720,8 @@ var _iosphere$elm_i18n$Main$exportResult = _elm_lang$core$Native_Platform.outgoi var _iosphere$elm_i18n$Main$operationExport = F2( function (source, format) { var exportFunction = function () { - var _p1 = format; - if (_p1.ctor === 'CSV') { + var _p2 = format; + if (_p2.ctor === 'CSV') { return _iosphere$elm_i18n$CSV_Export$generate; } else { return _iosphere$elm_i18n$PO_Export$generate; @@ -10368,37 +10740,54 @@ var _iosphere$elm_i18n$Main$importResult = _elm_lang$core$Native_Platform.outgoi return [v._0, v._1]; }); }); -var _iosphere$elm_i18n$Main$operationImport = F2( - function (csv, format) { +var _iosphere$elm_i18n$Main$operationImport = F3( + function (csv, mlangs, format) { var importFunction = function () { - var _p2 = format; - if (_p2.ctor === 'CSV') { + var _p3 = format; + if (_p3.ctor === 'CSV') { return _iosphere$elm_i18n$CSV_Import$generate; } else { return _iosphere$elm_i18n$PO_Import$generate; } }(); + var lang = A2( + _elm_lang$core$Maybe$withDefault, + 'Klingon', + _elm_lang$core$List$head( + A2( + _elm_lang$core$Maybe$withDefault, + {ctor: '[]'}, + mlangs))); return _iosphere$elm_i18n$Main$importResult( A2( _elm_lang$core$List$map, - _elm_lang$core$Tuple$mapFirst( - function (_p3) { - return A2( - _elm_lang$core$String$join, - '/', - A2(_elm_lang$core$String$split, '.', _p3)); - }), + _iosphere$elm_i18n$Main$slashifyModuleName, _iosphere$elm_i18n$Localized_Writer$generate( - importFunction( - A2( - _elm_lang$core$Maybe$withDefault, - '', - _elm_lang$core$List$head(csv)))))); + A2( + _elm_lang$core$List$map, + _iosphere$elm_i18n$Main$addLanguageToModuleName(lang), + importFunction( + A2( + _elm_lang$core$Maybe$withDefault, + '', + _elm_lang$core$List$head(csv))))))); + }); +var _iosphere$elm_i18n$Main$operationGenerateSwitch = F2( + function (sources, mlangs) { + var locales = A2( + _elm_lang$core$Maybe$withDefault, + {ctor: '[]'}, + mlangs); + return _iosphere$elm_i18n$Main$importResult( + A2( + _elm_lang$core$List$map, + _iosphere$elm_i18n$Main$slashifyModuleName, + A2(_iosphere$elm_i18n$Localized_Switch$generate, locales, sources))); }); var _iosphere$elm_i18n$Main$Model = {}; -var _iosphere$elm_i18n$Main$Flags = F3( - function (a, b, c) { - return {sources: a, operation: b, format: c}; +var _iosphere$elm_i18n$Main$Flags = F4( + function (a, b, c, d) { + return {sources: a, operation: b, format: c, languages: d}; }); var _iosphere$elm_i18n$Main$PO = {ctor: 'PO'}; var _iosphere$elm_i18n$Main$CSV = {ctor: 'CSV'}; @@ -10408,6 +10797,9 @@ var _iosphere$elm_i18n$Main$formatFromString = function (maybeFormat) { formatString, _elm_lang$core$Maybe$Just('PO')) ? _iosphere$elm_i18n$Main$PO : _iosphere$elm_i18n$Main$CSV; }; +var _iosphere$elm_i18n$Main$GenSwitch = function (a) { + return {ctor: 'GenSwitch', _0: a}; +}; var _iosphere$elm_i18n$Main$Import = function (a) { return {ctor: 'Import', _0: a}; }; @@ -10416,23 +10808,40 @@ var _iosphere$elm_i18n$Main$Export = function (a) { }; var _iosphere$elm_i18n$Main$operationFromString = F2( function (operation, formatString) { - return (_elm_lang$core$Native_Utils.eq(operation, 'import') ? _iosphere$elm_i18n$Main$Import : _iosphere$elm_i18n$Main$Export)( + return function () { + var _p4 = operation; + switch (_p4) { + case 'import': + return _iosphere$elm_i18n$Main$Import; + case 'export': + return _iosphere$elm_i18n$Main$Export; + default: + return _iosphere$elm_i18n$Main$GenSwitch; + } + }()( _iosphere$elm_i18n$Main$formatFromString(formatString)); }); var _iosphere$elm_i18n$Main$init = function (flags) { - var _p4 = A2(_iosphere$elm_i18n$Main$operationFromString, flags.operation, flags.format); - if (_p4.ctor === 'Export') { - return { - ctor: '_Tuple2', - _0: {}, - _1: A2(_iosphere$elm_i18n$Main$operationExport, flags.sources, _p4._0) - }; - } else { - return { - ctor: '_Tuple2', - _0: {}, - _1: A2(_iosphere$elm_i18n$Main$operationImport, flags.sources, _p4._0) - }; + var _p5 = A2(_iosphere$elm_i18n$Main$operationFromString, flags.operation, flags.format); + switch (_p5.ctor) { + case 'Export': + return { + ctor: '_Tuple2', + _0: {}, + _1: A2(_iosphere$elm_i18n$Main$operationExport, flags.sources, _p5._0) + }; + case 'Import': + return { + ctor: '_Tuple2', + _0: {}, + _1: A3(_iosphere$elm_i18n$Main$operationImport, flags.sources, flags.languages, _p5._0) + }; + default: + return { + ctor: '_Tuple2', + _0: {}, + _1: A2(_iosphere$elm_i18n$Main$operationGenerateSwitch, flags.sources, flags.languages) + }; } }; var _iosphere$elm_i18n$Main$main = _elm_lang$core$Platform$programWithFlags( @@ -10446,19 +10855,39 @@ var _iosphere$elm_i18n$Main$main = _elm_lang$core$Platform$programWithFlags( function (format) { return A2( _elm_lang$core$Json_Decode$andThen, - function (operation) { + function (languages) { return A2( _elm_lang$core$Json_Decode$andThen, - function (sources) { - return _elm_lang$core$Json_Decode$succeed( - {format: format, operation: operation, sources: sources}); + function (operation) { + return A2( + _elm_lang$core$Json_Decode$andThen, + function (sources) { + return _elm_lang$core$Json_Decode$succeed( + {format: format, languages: languages, operation: operation, sources: sources}); + }, + A2( + _elm_lang$core$Json_Decode$field, + 'sources', + _elm_lang$core$Json_Decode$list(_elm_lang$core$Json_Decode$string))); }, - A2( - _elm_lang$core$Json_Decode$field, - 'sources', - _elm_lang$core$Json_Decode$list(_elm_lang$core$Json_Decode$string))); + A2(_elm_lang$core$Json_Decode$field, 'operation', _elm_lang$core$Json_Decode$string)); }, - A2(_elm_lang$core$Json_Decode$field, 'operation', _elm_lang$core$Json_Decode$string)); + A2( + _elm_lang$core$Json_Decode$field, + 'languages', + _elm_lang$core$Json_Decode$oneOf( + { + ctor: '::', + _0: _elm_lang$core$Json_Decode$null(_elm_lang$core$Maybe$Nothing), + _1: { + ctor: '::', + _0: A2( + _elm_lang$core$Json_Decode$map, + _elm_lang$core$Maybe$Just, + _elm_lang$core$Json_Decode$list(_elm_lang$core$Json_Decode$string)), + _1: {ctor: '[]'} + } + }))); }, A2( _elm_lang$core$Json_Decode$field, diff --git a/example/Translation/De/Main.elm b/example/src/Translation/Main/De.elm similarity index 85% rename from example/Translation/De/Main.elm rename to example/src/Translation/Main/De.elm index e923f7f..537c29a 100644 --- a/example/Translation/De/Main.elm +++ b/example/src/Translation/Main/De.elm @@ -1,8 +1,7 @@ -module Translation.Main exposing (..) +module Translation.Main.De exposing (..) {-| -} - {-| A short greeting. -} greeting : String diff --git a/example/Translation/En/Main.elm b/example/src/Translation/Main/En.elm similarity index 54% rename from example/Translation/En/Main.elm rename to example/src/Translation/Main/En.elm index 7962c4e..603389d 100644 --- a/example/Translation/En/Main.elm +++ b/example/src/Translation/Main/En.elm @@ -1,8 +1,7 @@ -module Translation.Main exposing (..) +module Translation.Main.En exposing (..) {-| -} - {-| A short greeting. -} greeting : String @@ -10,9 +9,6 @@ greeting = "Hello" -{-| A personalized greeting. Note to transaltor: Use {{name}} as a placeholder -for the user's name. --} greetingWithName : String -> String greetingWithName name = "Hello, " diff --git a/extractor.js b/extractor.js index bcf3b33..a42c2c3 100755 --- a/extractor.js +++ b/extractor.js @@ -8,6 +8,7 @@ const argv = require("yargs") .option("format", {default: "csv", describe: "The format of the import/export operation. Supported formats are CSV and PO (case-insensitive)."}) .option("import", {alias: "i", describe: "A CSV file to be imported and to generate code from. Generate elm files will be placed in ."}) .option("importOutput", {default: "import", describe: "The base directory to which the generated code should be written. Subdirectories will be created per language and submodule."}) + .option("genswitch", {alias: "s", describe: "Generates Elm modules containing switches for all given languages."}) .option("language", {alias: "l", describe: "The language code of the current operation. This should match the subdirectory of the language in root."}) .option("root", {default: "Translation", describe: "The root to the translation modules. This script expects this directory to contain a subdirectory for each language."}) .demand("language") @@ -19,8 +20,8 @@ const fs = require("fs-extra"); const path = require("path"); const glob = require("glob"); -if (!argv.export && !argv.import) { - console.error("Please provide import or export option"); +if (!argv.export && !argv.import && !argv.genswitch) { + console.error("Please provide import, export or genswitch option"); process.exit(403); } @@ -46,6 +47,7 @@ if (argv.export) { // pass the array of file contents to our elm worker let worker = Elm.Main.worker({ + "languages": [], "sources": fileContents, "operation": "export", "format": argv.format, @@ -55,7 +57,7 @@ if (argv.export) { worker.ports.exportResult.subscribe(function(resultString) { handleExport(resultString); }); -} else { +} else if (argv.import) { // ensure that import is a valid file path if (!argv.import || argv.import == "" || argv.import == true) { console.error("Please provide an import path"); @@ -72,13 +74,40 @@ if (argv.export) { let csvContent = data.toString(); let worker = Elm.Main.worker({ + "languages": argv.language.split(","), "sources": [csvContent], "operation": "import", "format": argv.format, }); + let importDir = path.join(currentDir, argv.importOutput, argv.root); + worker.ports.importResult.subscribe(function(resultString) { + handleImport(resultString, importDir); + }); +} else { + let fullPath = path.join(currentDir, argv.root); + console.log("Parsing from", fullPath); + let fileNames = glob.sync(fullPath + "/Translation/**/{"+argv.language+"}.elm"); + console.log("└── Found elm module files for export:", fileNames); + + // read all files and store their content in an array + let fileContents = []; + fileNames.forEach(function(file) { + let data = fs.readFileSync(file); + let content = data.toString(); + fileContents.push(content); + }); + + let worker = Elm.Main.worker({ + "sources": fileContents, + "operation": "genswitch", + "languages": argv.language.split(","), + "format": argv.format, + }); + + let importDir = path.join(currentDir, argv.importOutput, argv.root); worker.ports.importResult.subscribe(function(resultString) { - handleImport(resultString); + handleImport(resultString, importDir); }); } @@ -104,16 +133,17 @@ function handleExport(resultString) { * * @param {[[String]]} results A list of (module name, file content) tuples. */ -function handleImport(results) { - let importDir = path.join(currentDir, argv.importOutput, argv.root, argv.language); +function handleImport(results, importDir) { fs.ensureDirSync(importDir); console.log("Writing elm-files files to:", importDir); results.forEach(function(result) { let moduleName = result[0]; if (moduleName.indexOf(argv.root) === 0) { - moduleName = moduleName.substr(argv.root.length + 1) + moduleName = moduleName.substr(argv.root.length + 1); } let filePath = path.join(importDir, moduleName + ".elm"); + // we also generate the top-level Translation.elm + filePath = filePath.replace(/\/(\.elm)$/, "$1"); fs.ensureDirSync(path.dirname(filePath)); fs.writeFileSync(filePath, result[1]); console.log("└── Finished writing:", filePath); diff --git a/src/Localized.elm b/src/Localized.elm index dd96b47..0589d6f 100644 --- a/src/Localized.elm +++ b/src/Localized.elm @@ -11,14 +11,20 @@ module Localized , Value , SourceCode , Placeholder + , LangCode , Module + , ModuleImplementation + , elementMeta + , languageModuleName , isEmptyFormatComponent + , elementRemoveLang + , namedModule ) {-| This module provides data structures describing localized string functions and constants. -@docs Element, Meta, Static, Format, FormatComponent, ModuleName, Key, Comment, Value, Placeholder, Module, SourceCode, isEmptyFormatComponent +@docs Element, Meta, Static, Format, FormatComponent, ModuleName, Key, Comment, Value, Placeholder, Module, ModuleImplementation, SourceCode, LangCode, isEmptyFormatComponent, elementMeta, languageModuleName, elementRemoveLang, namedModule -} @@ -109,6 +115,12 @@ type alias Module = ( ModuleName, List Element ) +{-| The representation of an Elm module with its complete source +-} +type alias ModuleImplementation = + ( ModuleName, SourceCode ) + + {-| A list of components make up a formatted element. See Format. -} type FormatComponent @@ -116,6 +128,12 @@ type FormatComponent | FormatComponentPlaceholder String +{-| A language code as a String used for module names, ie. "En" or "De" +-} +type alias LangCode = + String + + {-| Returns true if the component is empty. -} isEmptyFormatComponent : FormatComponent -> Bool @@ -126,3 +144,53 @@ isEmptyFormatComponent comp = FormatComponentPlaceholder string -> String.isEmpty string + + +{-| Returns an attribute of any Element +-} +elementMeta : (Meta -> val) -> Element -> val +elementMeta accessor element = + case element of + ElementStatic e -> + accessor e.meta + + ElementFormat e -> + accessor e.meta + + +{-| Returns an empty Module with a name indicating the locale. +-} +languageModuleName : ModuleName -> LangCode -> ModuleName +languageModuleName name lang = + name ++ "." ++ lang + + +{-| Constructs a new Module with the given name +-} +namedModule : ModuleName -> Module +namedModule name = + ( name, [] ) + + +{-| Removes a locale "En" from a module name like "Translation.Main.En" +-} +elementRemoveLang : LangCode -> Element -> Element +elementRemoveLang lang element = + let + moduleName = + elementMeta .moduleName element + + cleanedName = + String.split "." moduleName + |> List.filter (\p -> p /= lang) + |> String.join "." + + changeName meta name = + { meta | moduleName = name } + in + case element of + ElementStatic e -> + ElementStatic { e | meta = changeName e.meta cleanedName } + + ElementFormat e -> + ElementFormat { e | meta = changeName e.meta cleanedName } diff --git a/src/Localized/Switch.elm b/src/Localized/Switch.elm new file mode 100644 index 0000000..0a8b66e --- /dev/null +++ b/src/Localized/Switch.elm @@ -0,0 +1,161 @@ +module Localized.Switch exposing (generate) + +{-| + Reads in all the Translation.elm files and generates a master switch for + them. Elements only present in one of them will still be added. + +-} + +import Dict exposing (Dict) +import Localized exposing (..) +import Localized.Parser as Parser +import Localized.Writer.Module as Module +import Localized.Writer.Element as Element exposing (tab) + + +generate : List LangCode -> List SourceCode -> List ( ModuleName, SourceCode ) +generate languages sources = + mainModule languages + :: (sources + |> List.map Parser.parse + |> flatten2D + |> List.map (removeLocale languages) + |> unique + |> indexBy (elementMeta .moduleName) + |> Dict.toList + |> List.map (switchSource languages) + ) + + +unique : List Element -> List Element +unique elements = + u elements [] + + +u : List Element -> List Element -> List Element +u list have = + case list of + e :: rest -> + if member e have then + u rest have + else + u rest (e :: have) + + [] -> + have + + +flatten2D : List (List a) -> List a +flatten2D list = + List.foldr (++) [] list + + +member : Element -> List Element -> Bool +member e list = + let + sameElement e1 e2 = + case ( e1, e2 ) of + ( ElementFormat _, ElementStatic _ ) -> + False + + ( ElementStatic _, ElementFormat _ ) -> + False + + ( ElementStatic m1, ElementStatic m2 ) -> + m1.meta.moduleName == m2.meta.moduleName && m1.meta.key == m2.meta.key + + ( ElementFormat m1, ElementFormat m2 ) -> + m1.meta.moduleName == m2.meta.moduleName && m1.meta.key == m2.meta.key + in + List.any (sameElement e) list + + +indexBy : (Element -> comparable) -> List Element -> Dict comparable (List Element) +indexBy keymaker elements = + elements + |> List.foldr + (\e d -> + Dict.update (keymaker e) + (\v -> + case v of + Nothing -> + Just [ e ] + + Just l -> + Just (e :: l) + ) + d + ) + Dict.empty + + +switchSource : List LangCode -> Module -> ( ModuleName, SourceCode ) +switchSource languages mod = + let + ( moduleName, _ ) = + mod + in + ( moduleName + , Module.head mod + ++ Module.importModuleExposingAll ( "Translation", [] ) + ++ (String.join "" <| List.map (Module.importModule << namedModule << (languageModuleName moduleName)) languages) + ++ "\n\n" + ++ Module.elements (elementSource languages) mod + ) + + +elementSource : List LangCode -> Element -> SourceCode +elementSource languages element = + let + name = + elementMeta .key element + + moduleName = + elementMeta .moduleName element + + placeholders = + Element.placeholders element + in + name + ++ " : Language -> " + ++ placeholders + ++ "\n" + ++ name + ++ " language =\n" + ++ (tab ++ "case language of\n") + ++ (String.join "\n" <| + List.map + (\l -> + (tab ++ tab) + ++ l + ++ " -> " + ++ moduleName + ++ "." + ++ l + ++ "." + ++ name + ) + languages + ) + + +mainModule : List LangCode -> ( ModuleName, SourceCode ) +mainModule languages = + let + name = + "Translation" + + mod = + ( name, [] ) + in + ( name + , Module.head mod + ++ "type Language = " + ++ (String.join " | " languages) + ++ "\n" + ) + + +removeLocale : List LangCode -> Element -> Element +removeLocale langs element = + langs |> List.foldr elementRemoveLang element diff --git a/src/Localized/Writer.elm b/src/Localized/Writer.elm index 060f5dc..5c35120 100644 --- a/src/Localized/Writer.elm +++ b/src/Localized/Writer.elm @@ -8,6 +8,8 @@ elm modules implementing the localized elements. -} import Localized exposing (..) +import Localized.Writer.Module as Module +import Localized.Writer.Element as Element exposing (tab) {-| Generate elm-source code for a list of modules and their associated @@ -15,93 +17,32 @@ localized elements. -} generate : List Module -> List ( ModuleName, SourceCode ) generate = - List.map - (\( modulename, elements ) -> - ( modulename, moduleImplementation modulename elements ) - ) - - -moduleImplementation : ModuleName -> List Element -> SourceCode -moduleImplementation name elements = - "module " - ++ name - ++ " exposing (..)\n\n{-| -}\n\n\n" - ++ (List.map functionFromElement elements - |> String.join "\n\n\n" - |> String.trim - |> flip String.append "\n" - ) - - -functionFromElement : Element -> SourceCode -functionFromElement element = - case element of - ElementStatic static -> - functionStatic static - - ElementFormat format -> - functionFormat format - - -tab : SourceCode -tab = - " " + List.map moduleImplementation -functionStatic : Static -> SourceCode -functionStatic staticLocalized = - comment staticLocalized.meta.comment - ++ signature staticLocalized.meta.key [] - ++ ("\n" ++ tab ++ toString staticLocalized.value) - - -functionFormat : Format -> SourceCode -functionFormat format = - comment format.meta.comment - ++ signature format.meta.key format.placeholders - ++ "\n" - ++ (List.indexedMap formatComponentsImplementation format.components - |> String.join "\n" - ) - - -formatComponentsImplementation : Int -> FormatComponent -> SourceCode -formatComponentsImplementation index component = +moduleImplementation : Module -> ( ModuleName, SourceCode ) +moduleImplementation mod = let - prefix = - if index == 0 then - tab - else - tab ++ tab ++ "++ " + ( moduleName, _ ) = + mod in - case component of - FormatComponentStatic string -> - prefix ++ toString string - - FormatComponentPlaceholder string -> - prefix ++ String.trim string + ( moduleName + , Module.implementation element mod + ) -signature : Key -> List Placeholder -> SourceCode -signature key placeholders = +element : Element -> SourceCode +element element = let - num = - List.length placeholders - - types = - if num == 0 then - "String" - else - String.join " -> " (List.repeat (num + 1) "String") - - parameters = - if num == 0 then - "" - else - " " ++ String.join " " placeholders + c = + elementMeta .comment element in - (key ++ " : " ++ types ++ "\n") - ++ (key ++ parameters ++ " =") + comment c + ++ Element.typeDeclaration element + ++ "\n" + ++ Element.head element + ++ "\n" + ++ Element.body element comment : Comment -> SourceCode diff --git a/src/Localized/Writer/Element.elm b/src/Localized/Writer/Element.elm new file mode 100644 index 0000000..863e82b --- /dev/null +++ b/src/Localized/Writer/Element.elm @@ -0,0 +1,74 @@ +module Localized.Writer.Element exposing (..) + +import Localized exposing (..) + + +{-| Returns types of an translation element, ie. "helloWord : String" +-} +typeDeclaration : Element -> SourceCode +typeDeclaration element = + (elementMeta .key element) + ++ " : " + ++ placeholders element + + +placeholders : Element -> SourceCode +placeholders element = + case element of + ElementStatic _ -> + "String" + + ElementFormat { placeholders } -> + let + num = + List.length placeholders + in + String.join " -> " (List.repeat (num + 1) "String") + + +body : Element -> SourceCode +body element = + case element of + ElementStatic static -> + (tab ++ toString static.value) + + ElementFormat format -> + (List.indexedMap formatComponentsImplementation format.components + |> String.join "\n" + ) + + +head : Element -> SourceCode +head element = + (elementMeta .key element) + ++ (case element of + ElementStatic static -> + "" + + ElementFormat format -> + " " + ++ String.join "" format.placeholders + ) + ++ " =" + + +tab : SourceCode +tab = + " " + + +formatComponentsImplementation : Int -> FormatComponent -> SourceCode +formatComponentsImplementation index component = + let + prefix = + if index == 0 then + tab + else + tab ++ tab ++ "++ " + in + case component of + FormatComponentStatic string -> + prefix ++ toString string + + FormatComponentPlaceholder string -> + prefix ++ String.trim string diff --git a/src/Localized/Writer/Module.elm b/src/Localized/Writer/Module.elm new file mode 100644 index 0000000..9c4906d --- /dev/null +++ b/src/Localized/Writer/Module.elm @@ -0,0 +1,42 @@ +module Localized.Writer.Module exposing (..) + +{-| Provides code generation for Modules +-} + +import Localized exposing (..) + + +{-| Return the complete implementation for the Module, needs a function to implement each Element. +-} +implementation : (Element -> SourceCode) -> Module -> SourceCode +implementation functionImplementation mod = + head mod + ++ elements functionImplementation mod + + +elements : (Element -> SourceCode) -> Module -> SourceCode +elements functionImplementation ( name, elements ) = + (List.map functionImplementation elements + |> String.join "\n\n\n" + |> String.trim + |> flip String.append "\n" + ) + + +head : Module -> SourceCode +head ( name, _ ) = + "module " + ++ name + ++ " exposing (..)\n\n{-| -}\n\n" + + +importModule : Module -> SourceCode +importModule ( name, _ ) = + "import " ++ name ++ "\n" + + +importModuleExposingAll : Module -> SourceCode +importModuleExposingAll ( name, _ ) = + "import " + ++ name + ++ " exposing (..)\n" diff --git a/src/Main.elm b/src/Main.elm index 16a80d1..9eab003 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -9,8 +9,10 @@ in the CSV and PO submodules. import CSV.Export import CSV.Import import Json.Decode +import Localized exposing (..) import Localized.Parser as Localized import Localized.Writer +import Localized.Switch import PO.Export import PO.Import import Platform exposing (programWithFlags) @@ -28,6 +30,7 @@ type Format type Operation = Export Format | Import Format + | GenSwitch Format port exportResult : String -> Cmd msg @@ -39,10 +42,15 @@ port importResult : List ( String, String ) -> Cmd msg operationFromString : String -> Maybe String -> Operation operationFromString operation formatString = formatFromString formatString - |> if operation == "import" then - Import - else - Export + |> case operation of + "import" -> + Import + + "export" -> + Export + + _ -> + GenSwitch formatFromString : Maybe String -> Format @@ -61,6 +69,7 @@ type alias Flags = { sources : List String , operation : String , format : Maybe String + , languages : Maybe (List String) } @@ -79,7 +88,10 @@ init flags = ( {}, operationExport flags.sources format ) Import format -> - ( {}, operationImport flags.sources format ) + ( {}, operationImport flags.sources flags.languages format ) + + GenSwitch format -> + ( {}, operationGenerateSwitch flags.sources flags.languages ) operationExport : List String -> Format -> Cmd Never @@ -101,9 +113,12 @@ operationExport source format = exportResult exportValue -operationImport : List String -> Format -> Cmd Never -operationImport csv format = +operationImport : List String -> Maybe (List LangCode) -> Format -> Cmd Never +operationImport csv mlangs format = let + lang = + mlangs |> Maybe.withDefault [] |> List.head |> Maybe.withDefault "Klingon" + importFunction = case format of CSV -> @@ -115,11 +130,33 @@ operationImport csv format = List.head csv |> Maybe.withDefault "" |> importFunction + |> List.map (addLanguageToModuleName lang) |> Localized.Writer.generate - |> List.map (Tuple.mapFirst (String.split "." >> String.join "/")) + |> List.map slashifyModuleName + |> importResult + + +operationGenerateSwitch : List SourceCode -> Maybe (List LangCode) -> Cmd Never +operationGenerateSwitch sources mlangs = + let + locales = + Maybe.withDefault [] mlangs + in + Localized.Switch.generate locales sources + |> List.map slashifyModuleName |> importResult update : Never -> Model -> ( Model, Cmd Never ) update _ model = ( model, Cmd.none ) + + +slashifyModuleName : ModuleImplementation -> ModuleImplementation +slashifyModuleName = + Tuple.mapFirst (String.split "." >> String.join "/") + + +addLanguageToModuleName : LangCode -> Module -> Module +addLanguageToModuleName lang = + Tuple.mapFirst (flip languageModuleName lang) From c2f07b86477de4fc505565c7efa3d419acbd6788 Mon Sep 17 00:00:00 2001 From: Niklas Hofer Date: Wed, 25 Oct 2017 02:01:14 +0200 Subject: [PATCH 3/6] This extra newline is wanted by elm-format --- example/src/Translation/Main/De.elm | 1 + example/src/Translation/Main/En.elm | 1 + src/Localized/Writer/Module.elm | 1 + 3 files changed, 3 insertions(+) diff --git a/example/src/Translation/Main/De.elm b/example/src/Translation/Main/De.elm index 537c29a..f230033 100644 --- a/example/src/Translation/Main/De.elm +++ b/example/src/Translation/Main/De.elm @@ -2,6 +2,7 @@ module Translation.Main.De exposing (..) {-| -} + {-| A short greeting. -} greeting : String diff --git a/example/src/Translation/Main/En.elm b/example/src/Translation/Main/En.elm index 603389d..d772398 100644 --- a/example/src/Translation/Main/En.elm +++ b/example/src/Translation/Main/En.elm @@ -2,6 +2,7 @@ module Translation.Main.En exposing (..) {-| -} + {-| A short greeting. -} greeting : String diff --git a/src/Localized/Writer/Module.elm b/src/Localized/Writer/Module.elm index 9c4906d..bf05d76 100644 --- a/src/Localized/Writer/Module.elm +++ b/src/Localized/Writer/Module.elm @@ -11,6 +11,7 @@ import Localized exposing (..) implementation : (Element -> SourceCode) -> Module -> SourceCode implementation functionImplementation mod = head mod + ++ "\n" ++ elements functionImplementation mod From 1bb26dbf3e0981d047b42f278d8f98b084b71c32 Mon Sep 17 00:00:00 2001 From: Niklas Hofer Date: Wed, 25 Oct 2017 14:52:06 +0200 Subject: [PATCH 4/6] elm-format 0.6.1-alpha --- src/CSV/Export.elm | 1 + src/CSV/Import.elm | 15 +++++++-------- src/Localized.elm | 2 ++ src/Localized/Parser.elm | 1 + src/Localized/Switch.elm | 2 +- src/Localized/Writer.elm | 1 + src/Main.elm | 1 + src/PO/Export.elm | 3 ++- src/PO/Import.elm | 1 + src/PO/Import/Internal.elm | 1 + 10 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/CSV/Export.elm b/src/CSV/Export.elm index 819a957..d8b6e1d 100644 --- a/src/CSV/Export.elm +++ b/src/CSV/Export.elm @@ -4,6 +4,7 @@ module CSV.Export exposing (generate) (Localized.Element). @docs generate + -} import CSV.Template diff --git a/src/CSV/Import.elm b/src/CSV/Import.elm index e995005..7ceb532 100644 --- a/src/CSV/Import.elm +++ b/src/CSV/Import.elm @@ -140,14 +140,13 @@ formatElement modulename key comment placeholders value = String.split "}}" candidate |> withoutEmptyStrings -- ["p", " Goodbye "] -> [FormatComponentPlaceholder "p", FormatComponentStatic " Goodbye "] - |> - List.indexedMap - (\index submatch -> - if index % 2 == 0 then - FormatComponentPlaceholder (String.trim submatch) - else - FormatComponentStatic submatch - ) + |> List.indexedMap + (\index submatch -> + if index % 2 == 0 then + FormatComponentPlaceholder (String.trim submatch) + else + FormatComponentStatic submatch + ) else [ FormatComponentStatic candidate ] ) diff --git a/src/Localized.elm b/src/Localized.elm index 0589d6f..e08bf5c 100644 --- a/src/Localized.elm +++ b/src/Localized.elm @@ -25,6 +25,7 @@ module Localized and constants. @docs Element, Meta, Static, Format, FormatComponent, ModuleName, Key, Comment, Value, Placeholder, Module, ModuleImplementation, SourceCode, LangCode, isEmptyFormatComponent, elementMeta, languageModuleName, elementRemoveLang, namedModule + -} @@ -101,6 +102,7 @@ allows us to describe strings that contain dynamic values. , FormatComponentPlaceholder "name" ] } + -} type alias Format = { meta : Meta diff --git a/src/Localized/Parser.elm b/src/Localized/Parser.elm index 6900526..740995d 100644 --- a/src/Localized/Parser.elm +++ b/src/Localized/Parser.elm @@ -3,6 +3,7 @@ module Localized.Parser exposing (parse) {-| The parser parses elm code (one module) into a list of localized elements. @docs parse + -} import Localized exposing (..) diff --git a/src/Localized/Switch.elm b/src/Localized/Switch.elm index 0a8b66e..30e538a 100644 --- a/src/Localized/Switch.elm +++ b/src/Localized/Switch.elm @@ -1,9 +1,9 @@ module Localized.Switch exposing (generate) {-| + Reads in all the Translation.elm files and generates a master switch for them. Elements only present in one of them will still be added. - -} import Dict exposing (Dict) diff --git a/src/Localized/Writer.elm b/src/Localized/Writer.elm index 5c35120..ea506de 100644 --- a/src/Localized/Writer.elm +++ b/src/Localized/Writer.elm @@ -5,6 +5,7 @@ module names and associated localized elements and returns the source code for elm modules implementing the localized elements. @docs generate + -} import Localized exposing (..) diff --git a/src/Main.elm b/src/Main.elm index 9eab003..6502235 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -4,6 +4,7 @@ port module Main exposing (main) in the CSV and PO submodules. @docs main + -} import CSV.Export diff --git a/src/PO/Export.elm b/src/PO/Export.elm index b7877f1..c63fe58 100644 --- a/src/PO/Export.elm +++ b/src/PO/Export.elm @@ -2,9 +2,10 @@ module PO.Export exposing (generate) {-| The PO export generates PO strings from a list of localized elements (Localized.Element). For more information about the PO Format visit: -https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html + @docs generate + -} import Localized exposing (..) diff --git a/src/PO/Import.elm b/src/PO/Import.elm index d445ce0..1c077ae 100644 --- a/src/PO/Import.elm +++ b/src/PO/Import.elm @@ -9,6 +9,7 @@ Use Localized.Writer.write to create elm code from the list of localized elements. @docs generate + -} import Dict exposing (Dict) diff --git a/src/PO/Import/Internal.elm b/src/PO/Import/Internal.elm index f2fab47..ff87503 100644 --- a/src/PO/Import/Internal.elm +++ b/src/PO/Import/Internal.elm @@ -153,6 +153,7 @@ for our own exports where we write placeholders into the comment using the following format: #. i18n: placeholders: placeh1, placeh2 + -} placeholdersFromPoComment : String -> List Placeholder placeholdersFromPoComment poComment = From c0de93beb4dc2acf26f0218d40f8da0524ecad0a Mon Sep 17 00:00:00 2001 From: Niklas Hofer Date: Wed, 25 Oct 2017 15:18:34 +0200 Subject: [PATCH 5/6] Use full module names for types. As some claim these are easier to read. YMMV :) --- dist/elm.js | 5 ++- src/CSV/Export.elm | 16 +++++----- src/CSV/Import.elm | 30 +++++++++--------- src/Localized.elm | 7 +++-- src/Localized/Parser.elm | 4 +-- src/Localized/Parser/Internal.elm | 26 ++++++++-------- src/Localized/Switch.elm | 52 +++++++++++++++---------------- src/Localized/Writer.elm | 24 +++++++------- src/Localized/Writer/Element.elm | 34 ++++++++++---------- src/Localized/Writer/Module.elm | 16 +++++----- src/Main.elm | 12 +++---- src/PO/Export.elm | 24 +++++++------- src/PO/Import.elm | 6 ++-- src/PO/Import/Internal.elm | 42 ++++++++++++------------- 14 files changed, 152 insertions(+), 146 deletions(-) diff --git a/dist/elm.js b/dist/elm.js index 0f94abc..227101b 100644 --- a/dist/elm.js +++ b/dist/elm.js @@ -9893,7 +9893,10 @@ var _iosphere$elm_i18n$Localized_Writer_Module$implementation = F2( return A2( _elm_lang$core$Basics_ops['++'], _iosphere$elm_i18n$Localized_Writer_Module$head(mod), - A2(_iosphere$elm_i18n$Localized_Writer_Module$elements, functionImplementation, mod)); + A2( + _elm_lang$core$Basics_ops['++'], + '\n', + A2(_iosphere$elm_i18n$Localized_Writer_Module$elements, functionImplementation, mod))); }); var _iosphere$elm_i18n$Localized_Writer_Element$tab = ' '; diff --git a/src/CSV/Export.elm b/src/CSV/Export.elm index d8b6e1d..30d639b 100644 --- a/src/CSV/Export.elm +++ b/src/CSV/Export.elm @@ -8,7 +8,7 @@ module CSV.Export exposing (generate) -} import CSV.Template -import Localized exposing (..) +import Localized {-| Generate a CSV string from a list of localized elements (Localized.Element). @@ -19,7 +19,7 @@ Elm source code into a list of localized elements: |> CSV.Export.generate -} -generate : List Element -> String +generate : List Localized.Element -> String generate elements = List.map line elements |> List.map @@ -32,26 +32,26 @@ generate elements = |> flip String.append "\n" -line : Element -> List String +line : Localized.Element -> List String line element = case element of - ElementStatic static -> + Localized.ElementStatic static -> [ static.meta.moduleName, static.meta.key, static.meta.comment, "", static.value ] - ElementFormat format -> + Localized.ElementFormat format -> [ format.meta.moduleName, format.meta.key, format.meta.comment, String.join " " format.placeholders, formatString format.components ] -formatString : List FormatComponent -> String +formatString : List Localized.FormatComponent -> String formatString components = components |> List.map (\component -> case component of - FormatComponentStatic value -> + Localized.FormatComponentStatic value -> value - FormatComponentPlaceholder placeholder -> + Localized.FormatComponentPlaceholder placeholder -> CSV.Template.placeholder placeholder ) |> String.join "" diff --git a/src/CSV/Import.elm b/src/CSV/Import.elm index 7ceb532..8020a50 100644 --- a/src/CSV/Import.elm +++ b/src/CSV/Import.elm @@ -15,7 +15,7 @@ elements. import Csv import Dict -import Localized exposing (..) +import Localized import Set @@ -29,7 +29,7 @@ You will usually use this output to create elm code: |> Localized.Writer.write -} -generate : String -> List Module +generate : String -> List Localized.Module generate csv = case Csv.parse csv of Result.Ok lines -> @@ -40,7 +40,7 @@ generate csv = |> always [] -generateForCsv : Csv.Csv -> List Module +generateForCsv : Csv.Csv -> List Localized.Module generateForCsv lines = let modules = @@ -74,7 +74,7 @@ generateForCsv lines = modules -generateForModule : List (List String) -> List Element +generateForModule : List (List String) -> List Localized.Element generateForModule lines = List.filterMap fromLine lines @@ -94,12 +94,12 @@ moduleNameForLine columns = Nothing -linesForModule : ModuleName -> List (List String) -> List (List String) +linesForModule : Localized.ModuleName -> List (List String) -> List (List String) linesForModule moduleName lines = List.filter (\line -> moduleNameForLine line == Just moduleName) lines -fromLine : List String -> Maybe Element +fromLine : List String -> Maybe Localized.Element fromLine columns = case columns of modulename :: key :: comment :: placeholders :: value :: xs -> @@ -109,7 +109,7 @@ fromLine columns = Nothing -code : ModuleName -> Key -> Comment -> String -> Value -> Element +code : Localized.ModuleName -> Localized.Key -> Localized.Comment -> String -> Localized.Value -> Localized.Element code modulename key comment placeholderString value = let placeholders = @@ -126,7 +126,7 @@ code modulename key comment placeholderString value = formatElement modulename key comment placeholders value -formatElement : ModuleName -> Key -> Comment -> List Placeholder -> Value -> Element +formatElement : Localized.ModuleName -> Localized.Key -> Localized.Comment -> List Localized.Placeholder -> Localized.Value -> Localized.Element formatElement modulename key comment placeholders value = let components = @@ -139,20 +139,20 @@ formatElement modulename key comment placeholders value = -- "p}} Goodbye " -> ["p", " Goodbye "] String.split "}}" candidate |> withoutEmptyStrings - -- ["p", " Goodbye "] -> [FormatComponentPlaceholder "p", FormatComponentStatic " Goodbye "] + -- ["p", " Goodbye "] -> [FormatComponentPlaceholder "p", Localized.FormatComponentStatic " Goodbye "] |> List.indexedMap (\index submatch -> if index % 2 == 0 then - FormatComponentPlaceholder (String.trim submatch) + Localized.FormatComponentPlaceholder (String.trim submatch) else - FormatComponentStatic submatch + Localized.FormatComponentStatic submatch ) else - [ FormatComponentStatic candidate ] + [ Localized.FormatComponentStatic candidate ] ) |> List.concat in - ElementFormat + Localized.ElementFormat { meta = { moduleName = modulename , key = key @@ -163,9 +163,9 @@ formatElement modulename key comment placeholders value = } -staticElement : ModuleName -> Key -> Comment -> Value -> Element +staticElement : Localized.ModuleName -> Localized.Key -> Localized.Comment -> Localized.Value -> Localized.Element staticElement modulename key comment value = - ElementStatic + Localized.ElementStatic { meta = { moduleName = modulename , key = key diff --git a/src/Localized.elm b/src/Localized.elm index e08bf5c..d9f9741 100644 --- a/src/Localized.elm +++ b/src/Localized.elm @@ -1,8 +1,11 @@ module Localized exposing - ( Element(..) + ( Element(ElementStatic, ElementFormat) , Format - , FormatComponent(..) + , FormatComponent + ( FormatComponentStatic + , FormatComponentPlaceholder + ) , Meta , Static , ModuleName diff --git a/src/Localized/Parser.elm b/src/Localized/Parser.elm index 740995d..b1bc48a 100644 --- a/src/Localized/Parser.elm +++ b/src/Localized/Parser.elm @@ -6,14 +6,14 @@ module Localized.Parser exposing (parse) -} -import Localized exposing (..) +import Localized import Localized.Parser.Internal exposing (..) {-| Parses the source code of an elm module and returns a list of localized elements. -} -parse : SourceCode -> List Element +parse : Localized.SourceCode -> List Localized.Element parse source = let stringKeysAndParameters = diff --git a/src/Localized/Parser/Internal.elm b/src/Localized/Parser/Internal.elm index 5655916..7930bd4 100644 --- a/src/Localized/Parser/Internal.elm +++ b/src/Localized/Parser/Internal.elm @@ -1,7 +1,7 @@ module Localized.Parser.Internal exposing (..) import List.Extra as List -import Localized exposing (..) +import Localized import Regex exposing (Regex) import Utils.Regex as Utils @@ -33,7 +33,7 @@ regexFormats key = |> Regex.regex -findModuleName : SourceCode -> ModuleName +findModuleName : Localized.SourceCode -> Localized.ModuleName findModuleName source = Regex.find (Regex.AtMost 1) regexFindModuleName source |> List.head @@ -44,7 +44,7 @@ findModuleName source = {-| Finds all top level string declarations, both constants (`key : String` and functions returning strings (e.g. `fun : String -> String`). -} -stringDeclarations : SourceCode -> List ( Key, List String ) +stringDeclarations : Localized.SourceCode -> List ( Localized.Key, List String ) stringDeclarations source = Regex.find Regex.All regexStringDeclarations source |> List.filterMap @@ -65,7 +65,7 @@ stringDeclarations source = ) -findStaticElementForKey : ModuleName -> SourceCode -> Key -> Maybe Element +findStaticElementForKey : Localized.ModuleName -> Localized.SourceCode -> Localized.Key -> Maybe Localized.Element findStaticElementForKey moduleName source key = let maybeValue = @@ -75,15 +75,15 @@ findStaticElementForKey moduleName source key = in case maybeValue of Just value -> - Static (Meta moduleName key (findComment source key)) value - |> ElementStatic + Localized.Static (Localized.Meta moduleName key (findComment source key)) value + |> Localized.ElementStatic |> Just Nothing -> Nothing -findFormatElementForKey : ModuleName -> SourceCode -> Key -> Maybe Element +findFormatElementForKey : Localized.ModuleName -> Localized.SourceCode -> Localized.Key -> Maybe Localized.Element findFormatElementForKey moduleName source key = let regex = @@ -117,12 +117,12 @@ findFormatElementForKey moduleName source key = Nothing placeholderList -> - Format (Meta moduleName key (findComment source key)) placeholderList content - |> ElementFormat + Localized.Format (Localized.Meta moduleName key (findComment source key)) placeholderList content + |> Localized.ElementFormat |> Just -findComment : SourceCode -> Key -> Comment +findComment : Localized.SourceCode -> Localized.Key -> Localized.Comment findComment source key = let match = @@ -133,15 +133,15 @@ findComment source key = |> Maybe.withDefault "" -formatComponentFromString : String -> FormatComponent +formatComponentFromString : String -> Localized.FormatComponent formatComponentFromString value = if String.endsWith "\"" value && String.startsWith "\"" value then -- Remove quotes from value String.dropLeft 1 value |> String.dropRight 1 - |> FormatComponentStatic + |> Localized.FormatComponentStatic else - FormatComponentPlaceholder value + Localized.FormatComponentPlaceholder value trimmedStrings : List String -> List String diff --git a/src/Localized/Switch.elm b/src/Localized/Switch.elm index 30e538a..7ca4d64 100644 --- a/src/Localized/Switch.elm +++ b/src/Localized/Switch.elm @@ -7,13 +7,13 @@ module Localized.Switch exposing (generate) -} import Dict exposing (Dict) -import Localized exposing (..) +import Localized import Localized.Parser as Parser -import Localized.Writer.Module as Module -import Localized.Writer.Element as Element exposing (tab) +import Localized.Writer.Module +import Localized.Writer.Element exposing (tab) -generate : List LangCode -> List SourceCode -> List ( ModuleName, SourceCode ) +generate : List Localized.LangCode -> List Localized.SourceCode -> List ( Localized.ModuleName, Localized.SourceCode ) generate languages sources = mainModule languages :: (sources @@ -21,18 +21,18 @@ generate languages sources = |> flatten2D |> List.map (removeLocale languages) |> unique - |> indexBy (elementMeta .moduleName) + |> indexBy (Localized.elementMeta .moduleName) |> Dict.toList |> List.map (switchSource languages) ) -unique : List Element -> List Element +unique : List Localized.Element -> List Localized.Element unique elements = u elements [] -u : List Element -> List Element -> List Element +u : List Localized.Element -> List Localized.Element -> List Localized.Element u list have = case list of e :: rest -> @@ -50,27 +50,27 @@ flatten2D list = List.foldr (++) [] list -member : Element -> List Element -> Bool +member : Localized.Element -> List Localized.Element -> Bool member e list = let sameElement e1 e2 = case ( e1, e2 ) of - ( ElementFormat _, ElementStatic _ ) -> + ( Localized.ElementFormat _, Localized.ElementStatic _ ) -> False - ( ElementStatic _, ElementFormat _ ) -> + ( Localized.ElementStatic _, Localized.ElementFormat _ ) -> False - ( ElementStatic m1, ElementStatic m2 ) -> + ( Localized.ElementStatic m1, Localized.ElementStatic m2 ) -> m1.meta.moduleName == m2.meta.moduleName && m1.meta.key == m2.meta.key - ( ElementFormat m1, ElementFormat m2 ) -> + ( Localized.ElementFormat m1, Localized.ElementFormat m2 ) -> m1.meta.moduleName == m2.meta.moduleName && m1.meta.key == m2.meta.key in List.any (sameElement e) list -indexBy : (Element -> comparable) -> List Element -> Dict comparable (List Element) +indexBy : (Localized.Element -> comparable) -> List Localized.Element -> Dict comparable (List Localized.Element) indexBy keymaker elements = elements |> List.foldr @@ -89,32 +89,32 @@ indexBy keymaker elements = Dict.empty -switchSource : List LangCode -> Module -> ( ModuleName, SourceCode ) +switchSource : List Localized.LangCode -> Localized.Module -> ( Localized.ModuleName, Localized.SourceCode ) switchSource languages mod = let ( moduleName, _ ) = mod in ( moduleName - , Module.head mod - ++ Module.importModuleExposingAll ( "Translation", [] ) - ++ (String.join "" <| List.map (Module.importModule << namedModule << (languageModuleName moduleName)) languages) + , Localized.Writer.Module.head mod + ++ Localized.Writer.Module.importModuleExposingAll ( "Translation", [] ) + ++ (String.join "" <| List.map (Localized.Writer.Module.importModule << Localized.namedModule << (Localized.languageModuleName moduleName)) languages) ++ "\n\n" - ++ Module.elements (elementSource languages) mod + ++ Localized.Writer.Module.elements (elementSource languages) mod ) -elementSource : List LangCode -> Element -> SourceCode +elementSource : List Localized.LangCode -> Localized.Element -> Localized.SourceCode elementSource languages element = let name = - elementMeta .key element + Localized.elementMeta .key element moduleName = - elementMeta .moduleName element + Localized.elementMeta .moduleName element placeholders = - Element.placeholders element + Localized.Writer.Element.placeholders element in name ++ " : Language -> " @@ -139,7 +139,7 @@ elementSource languages element = ) -mainModule : List LangCode -> ( ModuleName, SourceCode ) +mainModule : List Localized.LangCode -> ( Localized.ModuleName, Localized.SourceCode ) mainModule languages = let name = @@ -149,13 +149,13 @@ mainModule languages = ( name, [] ) in ( name - , Module.head mod + , Localized.Writer.Module.head mod ++ "type Language = " ++ (String.join " | " languages) ++ "\n" ) -removeLocale : List LangCode -> Element -> Element +removeLocale : List Localized.LangCode -> Localized.Element -> Localized.Element removeLocale langs element = - langs |> List.foldr elementRemoveLang element + langs |> List.foldr Localized.elementRemoveLang element diff --git a/src/Localized/Writer.elm b/src/Localized/Writer.elm index ea506de..5e27dea 100644 --- a/src/Localized/Writer.elm +++ b/src/Localized/Writer.elm @@ -8,45 +8,45 @@ elm modules implementing the localized elements. -} -import Localized exposing (..) -import Localized.Writer.Module as Module -import Localized.Writer.Element as Element exposing (tab) +import Localized +import Localized.Writer.Module +import Localized.Writer.Element exposing (tab) {-| Generate elm-source code for a list of modules and their associated localized elements. -} -generate : List Module -> List ( ModuleName, SourceCode ) +generate : List Localized.Module -> List ( Localized.ModuleName, Localized.SourceCode ) generate = List.map moduleImplementation -moduleImplementation : Module -> ( ModuleName, SourceCode ) +moduleImplementation : Localized.Module -> ( Localized.ModuleName, Localized.SourceCode ) moduleImplementation mod = let ( moduleName, _ ) = mod in ( moduleName - , Module.implementation element mod + , Localized.Writer.Module.implementation element mod ) -element : Element -> SourceCode +element : Localized.Element -> Localized.SourceCode element element = let c = - elementMeta .comment element + Localized.elementMeta .comment element in comment c - ++ Element.typeDeclaration element + ++ Localized.Writer.Element.typeDeclaration element ++ "\n" - ++ Element.head element + ++ Localized.Writer.Element.head element ++ "\n" - ++ Element.body element + ++ Localized.Writer.Element.body element -comment : Comment -> SourceCode +comment : Localized.Comment -> Localized.SourceCode comment string = if String.isEmpty string then "" diff --git a/src/Localized/Writer/Element.elm b/src/Localized/Writer/Element.elm index 863e82b..affa3e9 100644 --- a/src/Localized/Writer/Element.elm +++ b/src/Localized/Writer/Element.elm @@ -1,24 +1,24 @@ module Localized.Writer.Element exposing (..) -import Localized exposing (..) +import Localized {-| Returns types of an translation element, ie. "helloWord : String" -} -typeDeclaration : Element -> SourceCode +typeDeclaration : Localized.Element -> Localized.SourceCode typeDeclaration element = - (elementMeta .key element) + (Localized.elementMeta .key element) ++ " : " ++ placeholders element -placeholders : Element -> SourceCode +placeholders : Localized.Element -> Localized.SourceCode placeholders element = case element of - ElementStatic _ -> + Localized.ElementStatic _ -> "String" - ElementFormat { placeholders } -> + Localized.ElementFormat { placeholders } -> let num = List.length placeholders @@ -26,38 +26,38 @@ placeholders element = String.join " -> " (List.repeat (num + 1) "String") -body : Element -> SourceCode +body : Localized.Element -> Localized.SourceCode body element = case element of - ElementStatic static -> + Localized.ElementStatic static -> (tab ++ toString static.value) - ElementFormat format -> + Localized.ElementFormat format -> (List.indexedMap formatComponentsImplementation format.components |> String.join "\n" ) -head : Element -> SourceCode +head : Localized.Element -> Localized.SourceCode head element = - (elementMeta .key element) + (Localized.elementMeta .key element) ++ (case element of - ElementStatic static -> + Localized.ElementStatic static -> "" - ElementFormat format -> + Localized.ElementFormat format -> " " ++ String.join "" format.placeholders ) ++ " =" -tab : SourceCode +tab : Localized.SourceCode tab = " " -formatComponentsImplementation : Int -> FormatComponent -> SourceCode +formatComponentsImplementation : Int -> Localized.FormatComponent -> Localized.SourceCode formatComponentsImplementation index component = let prefix = @@ -67,8 +67,8 @@ formatComponentsImplementation index component = tab ++ tab ++ "++ " in case component of - FormatComponentStatic string -> + Localized.FormatComponentStatic string -> prefix ++ toString string - FormatComponentPlaceholder string -> + Localized.FormatComponentPlaceholder string -> prefix ++ String.trim string diff --git a/src/Localized/Writer/Module.elm b/src/Localized/Writer/Module.elm index bf05d76..fbf5435 100644 --- a/src/Localized/Writer/Module.elm +++ b/src/Localized/Writer/Module.elm @@ -1,21 +1,21 @@ module Localized.Writer.Module exposing (..) -{-| Provides code generation for Modules +{-| Provides code generation for Localized.Modules -} -import Localized exposing (..) +import Localized -{-| Return the complete implementation for the Module, needs a function to implement each Element. +{-| Return the complete implementation for the Localized.Module, needs a function to implement each Localized.Element. -} -implementation : (Element -> SourceCode) -> Module -> SourceCode +implementation : (Localized.Element -> Localized.SourceCode) -> Localized.Module -> Localized.SourceCode implementation functionImplementation mod = head mod ++ "\n" ++ elements functionImplementation mod -elements : (Element -> SourceCode) -> Module -> SourceCode +elements : (Localized.Element -> Localized.SourceCode) -> Localized.Module -> Localized.SourceCode elements functionImplementation ( name, elements ) = (List.map functionImplementation elements |> String.join "\n\n\n" @@ -24,19 +24,19 @@ elements functionImplementation ( name, elements ) = ) -head : Module -> SourceCode +head : Localized.Module -> Localized.SourceCode head ( name, _ ) = "module " ++ name ++ " exposing (..)\n\n{-| -}\n\n" -importModule : Module -> SourceCode +importModule : Localized.Module -> Localized.SourceCode importModule ( name, _ ) = "import " ++ name ++ "\n" -importModuleExposingAll : Module -> SourceCode +importModuleExposingAll : Localized.Module -> Localized.SourceCode importModuleExposingAll ( name, _ ) = "import " ++ name diff --git a/src/Main.elm b/src/Main.elm index 6502235..d441a44 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -10,7 +10,7 @@ in the CSV and PO submodules. import CSV.Export import CSV.Import import Json.Decode -import Localized exposing (..) +import Localized import Localized.Parser as Localized import Localized.Writer import Localized.Switch @@ -114,7 +114,7 @@ operationExport source format = exportResult exportValue -operationImport : List String -> Maybe (List LangCode) -> Format -> Cmd Never +operationImport : List String -> Maybe (List Localized.LangCode) -> Format -> Cmd Never operationImport csv mlangs format = let lang = @@ -137,7 +137,7 @@ operationImport csv mlangs format = |> importResult -operationGenerateSwitch : List SourceCode -> Maybe (List LangCode) -> Cmd Never +operationGenerateSwitch : List Localized.SourceCode -> Maybe (List Localized.LangCode) -> Cmd Never operationGenerateSwitch sources mlangs = let locales = @@ -153,11 +153,11 @@ update _ model = ( model, Cmd.none ) -slashifyModuleName : ModuleImplementation -> ModuleImplementation +slashifyModuleName : Localized.ModuleImplementation -> Localized.ModuleImplementation slashifyModuleName = Tuple.mapFirst (String.split "." >> String.join "/") -addLanguageToModuleName : LangCode -> Module -> Module +addLanguageToModuleName : Localized.LangCode -> Localized.Module -> Localized.Module addLanguageToModuleName lang = - Tuple.mapFirst (flip languageModuleName lang) + Tuple.mapFirst (flip Localized.languageModuleName lang) diff --git a/src/PO/Export.elm b/src/PO/Export.elm index c63fe58..37d1940 100644 --- a/src/PO/Export.elm +++ b/src/PO/Export.elm @@ -1,14 +1,14 @@ module PO.Export exposing (generate) {-| The PO export generates PO strings from a list of localized elements -(Localized.Element). For more information about the PO Format visit: +(Localized.Element). For more information about the PO Localized.Format visit: @docs generate -} -import Localized exposing (..) +import Localized import PO.Template @@ -20,24 +20,24 @@ Elm source code into a list of localized elements: |> PO.Export.generate -} -generate : List Element -> String +generate : List Localized.Element -> String generate elements = List.map line elements |> String.join "\n\n" |> flip String.append "\n" -line : Element -> String +line : Localized.Element -> String line element = case element of - ElementStatic static -> + Localized.ElementStatic static -> commentLine static.meta.comment ++ "\n" ++ identifier static.meta.moduleName static.meta.key ++ "\n" ++ staticElement static.value - ElementFormat format -> + Localized.ElementFormat format -> commentLine format.meta.comment ++ "\n" ++ commentLine (PO.Template.placeholderCommentPrefix ++ String.join " " format.placeholders) @@ -47,7 +47,7 @@ line element = ++ ("msgstr " ++ formatElement format.components) -commentLine : Comment -> String +commentLine : Localized.Comment -> String commentLine comment = String.split "\n" comment |> String.join "\n#. " @@ -55,26 +55,26 @@ commentLine comment = |> String.trim -identifier : ModuleName -> Key -> String +identifier : Localized.ModuleName -> Localized.Key -> String identifier modulename key = "msgid \"" ++ modulename ++ "." ++ key ++ "\"" -staticElement : Value -> String +staticElement : Localized.Value -> String staticElement value = "msgstr " ++ toString value -formatElement : List FormatComponent -> String +formatElement : List Localized.FormatComponent -> String formatElement list = list |> List.map (\element -> case element of - FormatComponentPlaceholder placeholder -> + Localized.FormatComponentPlaceholder placeholder -> PO.Template.placeholder placeholder - FormatComponentStatic string -> + Localized.FormatComponentStatic string -> string ) |> String.join "" diff --git a/src/PO/Import.elm b/src/PO/Import.elm index 1c077ae..1148b84 100644 --- a/src/PO/Import.elm +++ b/src/PO/Import.elm @@ -13,7 +13,7 @@ elements. -} import Dict exposing (Dict) -import Localized exposing (..) +import Localized import PO.Import.Internal exposing (..) @@ -28,7 +28,7 @@ You will usually use this output to create elm code: |> Localized.Writer.write -} -generate : String -> List Module +generate : String -> List Localized.Module generate poString = let keysInModules = @@ -42,7 +42,7 @@ generate poString = keysInModules -generateModule : String -> ModuleName -> List Key -> List Element +generateModule : String -> Localized.ModuleName -> List Localized.Key -> List Localized.Element generateModule poString moduleName allKeys = let fullComments = diff --git a/src/PO/Import/Internal.elm b/src/PO/Import/Internal.elm index ff87503..1485ee3 100644 --- a/src/PO/Import/Internal.elm +++ b/src/PO/Import/Internal.elm @@ -9,7 +9,7 @@ module PO.Import.Internal ) import Dict exposing (Dict) -import Localized exposing (..) +import Localized import Regex exposing (Regex) import Set import String.Extra as String @@ -17,7 +17,7 @@ import Utils.Regex import PO.Template -element : ModuleName -> Key -> Value -> Comment -> Element +element : Localized.ModuleName -> Localized.Key -> Localized.Value -> Localized.Comment -> Localized.Element element moduleName key value fullComment = let comment = @@ -49,12 +49,12 @@ element moduleName key value fullComment = } in if List.isEmpty placeholders then - ElementStatic + Localized.ElementStatic { meta = meta , value = unquotedValue } else - ElementFormat + Localized.ElementFormat { meta = meta , placeholders = placeholders , components = formatComponentsFromValue unquotedValue placeholders @@ -65,7 +65,7 @@ element moduleName key value fullComment = ---- KEYS -fullKey : ModuleName -> Key -> String +fullKey : Localized.ModuleName -> Localized.Key -> String fullKey moduleName key = moduleName ++ "." ++ key @@ -73,7 +73,7 @@ fullKey moduleName key = {-| Extract a list of all localization keys per module from a PO string file's contents. -} -keys : String -> List ( ModuleName, List String ) +keys : String -> List ( Localized.ModuleName, List String ) keys poString = let matches = @@ -119,7 +119,7 @@ keys poString = file, for all given keys in a module. The dict will contain use the localized key for key and the value will be the multiline comment. -} -poComments : String -> ModuleName -> List Key -> Dict Key String +poComments : String -> Localized.ModuleName -> List Localized.Key -> Dict Localized.Key String poComments poString moduleName allKeys = allKeys |> List.map @@ -133,7 +133,7 @@ poComments poString moduleName allKeys = |> Dict.fromList -commentFromPoComment : String -> Comment +commentFromPoComment : String -> Localized.Comment commentFromPoComment poComment = String.trim poComment |> String.split "#." @@ -155,7 +155,7 @@ following format: #. i18n: placeholders: placeh1, placeh2 -} -placeholdersFromPoComment : String -> List Placeholder +placeholdersFromPoComment : String -> List Localized.Placeholder placeholdersFromPoComment poComment = let placeholdersPrefix = @@ -185,7 +185,7 @@ placeholdersFromPoComment poComment = {-| Extract all values for a module and a given list of keys from a PO file. The dict will reference the value by its localization key. -} -values : String -> ModuleName -> List Key -> Dict Key Value +values : String -> Localized.ModuleName -> List Localized.Key -> Dict Localized.Key Localized.Value values poString moduleName allKeys = allKeys |> List.map @@ -204,18 +204,18 @@ values poString moduleName allKeys = placeholder definition is missing form the comment, but ma lead to issues with sortine. For this reason, we only use this as a fallback an log an error. -} -placeholdersInValue : Value -> List Placeholder +placeholdersInValue : Localized.Value -> List Localized.Placeholder placeholdersInValue value = Regex.find Regex.All regexForPlaceholder value |> List.filterMap (\match -> Utils.Regex.submatchAt 0 (Just match)) -formatComponentsFromValue : Value -> List Placeholder -> List FormatComponent +formatComponentsFromValue : Localized.Value -> List Localized.Placeholder -> List Localized.FormatComponent formatComponentsFromValue value placeholders = - findPlaceholdersInStaticComponents [ FormatComponentStatic value ] placeholders + findPlaceholdersInStaticComponents [ Localized.FormatComponentStatic value ] placeholders -findPlaceholdersInStaticComponents : List FormatComponent -> List Placeholder -> List FormatComponent +findPlaceholdersInStaticComponents : List Localized.FormatComponent -> List Localized.Placeholder -> List Localized.FormatComponent findPlaceholdersInStaticComponents components placeholders = case List.head placeholders of Nothing -> @@ -227,16 +227,16 @@ findPlaceholdersInStaticComponents components placeholders = List.map (\component -> case component of - FormatComponentPlaceholder _ -> + Localized.FormatComponentPlaceholder _ -> [ component ] - FormatComponentStatic value -> + Localized.FormatComponentStatic value -> let subComponents = String.split (PO.Template.placeholder nextPlaceholder) value - |> List.map FormatComponentStatic - |> List.intersperse (FormatComponentPlaceholder nextPlaceholder) - |> List.filter (isEmptyFormatComponent >> not) + |> List.map Localized.FormatComponentStatic + |> List.intersperse (Localized.FormatComponentPlaceholder nextPlaceholder) + |> List.filter (Localized.isEmptyFormatComponent >> not) in subComponents ) @@ -250,13 +250,13 @@ findPlaceholdersInStaticComponents components placeholders = ---- REGEX -regexComments : Key -> Regex +regexComments : Localized.Key -> Regex regexComments key = -- Find all lines preceeding `msgid "key"` that start with `#.` Regex.regex ("((?:#\\.[^\\n]*\\n)*)msgid " ++ toString key) -regexForValue : Key -> Regex +regexForValue : Localized.Key -> Regex regexForValue key = -- Find all lines succeeding `msgid "key" \nmsgstr` until the two successive white lines Regex.regex From 9199b8770ba00873956c6cf92b19fb9e3d79473e Mon Sep 17 00:00:00 2001 From: Niklas Hofer Date: Wed, 25 Oct 2017 15:43:28 +0200 Subject: [PATCH 6/6] Reduce elm-analyze warnings. --- src/Localized.elm | 4 +++- src/Localized/Switch.elm | 8 ++++++-- src/Localized/Writer.elm | 2 +- src/Localized/Writer/Element.elm | 12 ++++++++++-- src/Localized/Writer/Module.elm | 14 ++++++++++---- src/Main.elm | 2 +- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Localized.elm b/src/Localized.elm index d9f9741..a0a620d 100644 --- a/src/Localized.elm +++ b/src/Localized.elm @@ -91,7 +91,9 @@ type alias Meta = It contains a single string value. -} type alias Static = - { meta : Meta, value : Value } + { meta : Meta + , value : Value + } {-| A formatted string can contain placeholders and static components. This diff --git a/src/Localized/Switch.elm b/src/Localized/Switch.elm index 7ca4d64..08d83e5 100644 --- a/src/Localized/Switch.elm +++ b/src/Localized/Switch.elm @@ -98,7 +98,11 @@ switchSource languages mod = ( moduleName , Localized.Writer.Module.head mod ++ Localized.Writer.Module.importModuleExposingAll ( "Translation", [] ) - ++ (String.join "" <| List.map (Localized.Writer.Module.importModule << Localized.namedModule << (Localized.languageModuleName moduleName)) languages) + ++ (String.join "" <| + List.map + (Localized.Writer.Module.importModule << Localized.namedModule << Localized.languageModuleName moduleName) + languages + ) ++ "\n\n" ++ Localized.Writer.Module.elements (elementSource languages) mod ) @@ -151,7 +155,7 @@ mainModule languages = ( name , Localized.Writer.Module.head mod ++ "type Language = " - ++ (String.join " | " languages) + ++ String.join " | " languages ++ "\n" ) diff --git a/src/Localized/Writer.elm b/src/Localized/Writer.elm index 5e27dea..e53c820 100644 --- a/src/Localized/Writer.elm +++ b/src/Localized/Writer.elm @@ -10,7 +10,7 @@ elm modules implementing the localized elements. import Localized import Localized.Writer.Module -import Localized.Writer.Element exposing (tab) +import Localized.Writer.Element {-| Generate elm-source code for a list of modules and their associated diff --git a/src/Localized/Writer/Element.elm b/src/Localized/Writer/Element.elm index affa3e9..56705c3 100644 --- a/src/Localized/Writer/Element.elm +++ b/src/Localized/Writer/Element.elm @@ -1,4 +1,12 @@ -module Localized.Writer.Element exposing (..) +module Localized.Writer.Element + exposing + ( typeDeclaration + , placeholders + , body + , head + , tab + , formatComponentsImplementation + ) import Localized @@ -42,7 +50,7 @@ head : Localized.Element -> Localized.SourceCode head element = (Localized.elementMeta .key element) ++ (case element of - Localized.ElementStatic static -> + Localized.ElementStatic _ -> "" Localized.ElementFormat format -> diff --git a/src/Localized/Writer/Module.elm b/src/Localized/Writer/Module.elm index fbf5435..5193726 100644 --- a/src/Localized/Writer/Module.elm +++ b/src/Localized/Writer/Module.elm @@ -1,4 +1,11 @@ -module Localized.Writer.Module exposing (..) +module Localized.Writer.Module + exposing + ( implementation + , elements + , head + , importModule + , importModuleExposingAll + ) {-| Provides code generation for Localized.Modules -} @@ -16,12 +23,11 @@ implementation functionImplementation mod = elements : (Localized.Element -> Localized.SourceCode) -> Localized.Module -> Localized.SourceCode -elements functionImplementation ( name, elements ) = - (List.map functionImplementation elements +elements functionImplementation ( _, elements ) = + List.map functionImplementation elements |> String.join "\n\n\n" |> String.trim |> flip String.append "\n" - ) head : Localized.Module -> Localized.SourceCode diff --git a/src/Main.elm b/src/Main.elm index d441a44..ba1e75c 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -91,7 +91,7 @@ init flags = Import format -> ( {}, operationImport flags.sources flags.languages format ) - GenSwitch format -> + GenSwitch _ -> ( {}, operationGenerateSwitch flags.sources flags.languages )