From 1e1bb29b8cd1a71d9090def225e63b78f0eccff2 Mon Sep 17 00:00:00 2001 From: Anthony D'Ambrosio Date: Thu, 3 Mar 2016 15:36:23 -0800 Subject: [PATCH 1/9] 0.1.3-SNAPSHOT --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 8756725..e2bf08f 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject navis/untangled-lein-i18n "0.1.2" +(defproject navis/untangled-lein-i18n "0.1.3-SNAPSHOT" :description "A plugin for extracting/populating transalations for Untangled" :url "" :license {:name "MIT" From 74d25957bd87d45d164a55e74a508c53b1927fc6 Mon Sep 17 00:00:00 2001 From: Anthony D'Ambrosio Date: Fri, 4 Mar 2016 13:40:22 -0800 Subject: [PATCH 2/9] Removing bin/publish-local, use team/scripts/publish-local --- bin/publish-local | 3 --- 1 file changed, 3 deletions(-) delete mode 100755 bin/publish-local diff --git a/bin/publish-local b/bin/publish-local deleted file mode 100755 index dad2b84..0000000 --- a/bin/publish-local +++ /dev/null @@ -1,3 +0,0 @@ -lein pom -lein jar -lein localrepo install -p pom.xml target/untangled-lein-i18n-0.1.2.jar untangled-lein-i18n 0.1.2 From 8c241d785c79f9412e4e6acc1ce7d5d980f7bca3 Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Thu, 19 May 2016 14:50:34 -0700 Subject: [PATCH 3/9] Story ID: develop updated deps --- .gitignore | 1 + project.clj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 11c951e..e3f2185 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ target .lein-env examples/calendar/resources/public/js/specs examples/todo/src/quiescent_model +pom.xml.asc diff --git a/project.clj b/project.clj index e2bf08f..11aa24c 100644 --- a/project.clj +++ b/project.clj @@ -4,7 +4,7 @@ :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} - :dependencies [[org.clojure/clojure "1.7.0"] + :dependencies [[org.clojure/clojure "1.8.0"] [navis/untangled-spec "0.3.5" :scope "test"] [leiningen-core "2.5.3"] [lein-cljsbuild "1.1.2"] From 56e583eba3341b03c80c96585c1e153461f5ab3c Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Mon, 21 Nov 2016 12:47:26 -0500 Subject: [PATCH 4/9] fixed up various features --- project.clj | 6 +- src/leiningen/i18n.clj | 218 +++++++++++++++++--------------- src/leiningen/i18n/code_gen.clj | 30 ++--- src/leiningen/i18n/util.clj | 51 +++++--- 4 files changed, 163 insertions(+), 142 deletions(-) diff --git a/project.clj b/project.clj index 11aa24c..6f9076e 100644 --- a/project.clj +++ b/project.clj @@ -1,13 +1,13 @@ -(defproject navis/untangled-lein-i18n "0.1.3-SNAPSHOT" +(defproject navis/untangled-lein-i18n "0.2.0-SNAPSHOT" :description "A plugin for extracting/populating transalations for Untangled" :url "" :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} :dependencies [[org.clojure/clojure "1.8.0"] - [navis/untangled-spec "0.3.5" :scope "test"] + [navis/untangled-spec "0.3.9" :scope "test"] [leiningen-core "2.5.3"] - [lein-cljsbuild "1.1.2"] + [lein-cljsbuild "1.1.4"] [leiningen "2.5.3"]] :plugins [[com.jakemccrary/lein-test-refresh "0.14.0"]] diff --git a/src/leiningen/i18n.clj b/src/leiningen/i18n.clj index 909158b..20cfa04 100644 --- a/src/leiningen/i18n.clj +++ b/src/leiningen/i18n.clj @@ -2,90 +2,131 @@ (:require [clojure.java.shell :refer [sh]] [leiningen.core.main :as lmain] [clojure.string :as string] - [leiningen.cljsbuild :refer [cljsbuild]] [clojure.string :as str] + [clojure.java.io :as io] [leiningen.i18n.code-gen :as cg] [leiningen.i18n.parse-po :as parse] - [leiningen.i18n.util :as util] - [clojure.pprint :as pp])) - -(def msgs-dir-path "i18n/msgs") -(def messages-pot-path (str msgs-dir-path "/messages.pot")) -(def compiled-js-path "i18n/out/compiled.js") - -(defn- po-path [po-file] (str msgs-dir-path "/" po-file)) - -(defn- puke [msg] - (lmain/warn msg) - (lmain/abort)) - -(defn configure-i18n-build - " - Create an in-memory clsjbuild configuration. - - Parameters: - * `build` - a [:cljsbuild :builds] map - - Returns a new cljsbuild configuration that will ensure all cljs is compiled into a single JS file, from which we will - extract translatable strings. - " - [build] - (let [compiler-config (assoc (:compiler build) :output-dir "i18n/out" - :optimizations :whitespace - :output-to compiled-js-path)] - (assoc build :id "i18n" :compiler compiler-config))) - - - -(defn lookup-modules + [leiningen.i18n.util :as util :refer [puke po-path]] + [clojure.pprint :as pp]) + (:import (java.io File))) + +(def i18n-defaults + {:default-locale "en" + :translation-namespace "untangled.translations" + :source-folder nil + :translation-build "i18n" + :production-build "production" + :po-files "i18n/msgs"}) + +(defn check-i18n-build! [nm build] + (when (nil? build) + (puke "The specified translation build (" nm ") does not exist in the project file.")) + (when (nil? (get-in build [:compiler :output-to])) + (puke "The translation build (" nm ") has no :output-to setting.")) + (when-not (#{:whitespace :simple} (get-in build [:compiler :optimizations])) + (puke "The translation build (" nm ") should specify optimizations of :whitespace or :simple"))) + +(defn check-production-build! [nm build] + (when (nil? build) + (puke "The specified production build (" nm ") does not exist in the project file.")) + (when (nil? (get-in build [:compiler :output-to])) + (puke "The production build (" nm ") has no :output-to setting.")) + (when-not (#{:whitespace :simple :advanced} (get-in build [:compiler :optimizations])) + (puke "The production build (" nm ") should specify optimizations of :whitespace, :simple, or :advanced")) + (when-not (get-in build [:compiler :modules]) + (lmain/warn "The production build (" nm ") does not specify modules. Your program will have to require all translations at the top level to compile them all in."))) + +(defn setup-environment! + "Check and setup the environment. Returns a subset of settings pertaining to the filesystem: + + :messages-pot The full path to the messages.pot (generated) file + :po-dir a File object representing the po directory for translation files." + [settings] + (when (or (nil? (:source-folder settings)) + (not (.exists (io/as-file (:source-folder settings))))) + (puke "The configured target folder [:untangled-i18n :source-folder] for translation cljs sources (" + (:source-folder settings) ") is not set or does not exist.")) + (when (util/gettext-missing?) + (puke "The xgettext and msgmerge commands are not installed, or are not on your $PATH.")) + (let [po-files (:po-files settings) + po-dir ^File (io/as-file po-files)] + (when (not (.exists po-dir)) + (lmain/warn "Creating missing PO directory: " po-files) + (.mkdirs po-dir)) + (when (not (.isDirectory po-dir)) + (puke po-files " is NOT a directory!")) + {:po-dir po-dir + :messages-pot (.getAbsolutePath (new File po-dir "messages.pot"))})) + +(defn check-settings! + "Verify that the project configured settings are all present, or that the defaults make sense. Returns settings + with various additional things set from the environment and project file: + + :translation-js will be the path to the Javascript output of the i18n build " - Check if the production cljs build contains a :modules configuration map. - - Parameters: - * `project` - a leiningen project map - * `locales` - a list of locale strings + [settings builds] + (let [i18n-build-name (:translation-build settings) + prod-build-name (:production-build settings) + i18n-build (util/get-cljsbuild builds i18n-build-name) + production-build (util/get-cljsbuild builds prod-build-name) + module-basepath (or (-> production-build :compiler :asset-path) "/") + trans-ns (:translation-namespace settings) + src-base (:source-folder settings) + output-dir (util/cljs-output-dir src-base trans-ns) + outdir ^File (io/as-file output-dir) + updated-settings (merge settings + {:translation-target output-dir + :module-basepath module-basepath + :translation-js (get-in i18n-build [:compiler :output-to])} + (setup-environment! settings))] + (when (not (.exists outdir)) + (lmain/info "Making missing source folder " outdir) + (.mkdirs outdir)) + (lmain/info "Locale modules will be build to load relative to asset path: " module-basepath) + (lmain/info "Translation build: " i18n-build-name) + (check-i18n-build! i18n-build-name i18n-build) + (check-production-build! prod-build-name production-build) + (lmain/info "Settings for i18n: " updated-settings) + updated-settings)) - If the production cljs build has :modules, return nil, else return the suggested :modules configuration. - " - [project locales] - (let [ns (util/translation-namespace project) - build (util/get-cljsbuild (get-in project [:cljsbuild :builds]) (util/target-build project)) - ] - (if (-> build :compiler (contains? :modules)) - nil - (let [output-dir (:output-dir (:compiler build)) - js-file #(str output-dir "/" % ".js") - name (:name project) - main-name (str name ".main") - main {:output-to (js-file name) - :entries #{main-name}} - modules (reduce #(assoc %1 - (keyword %2) {:output-to (js-file %2) - :entries #{(str ns "." %2)}}) {} locales) - modules-with-main (assoc modules :main main)] - (-> build - (update-in [:compiler] dissoc :main) - (assoc-in [:compiler :modules] modules-with-main) - (assoc-in [:compiler :optimizations] :advanced)))))) +(defn extract-strings + "This subtask extracts strings from your cljs files that should be translated." + [project] + (let [explicit-settings (get project :untangled-i18n {}) + builds (get-in project [:cljsbuild :builds]) + settings-with-defaults (merge i18n-defaults explicit-settings) + settings (check-settings! settings-with-defaults builds) + messages-pot-path (:messages-pot settings) + js-path (:translation-js settings) + po-files-to-merge (util/find-po-files (:po-files settings))] + (util/build project (:translation-build settings)) + (util/run "xgettext" "--from-code=UTF-8" "--debug" "-k" "-ktr:1" "-ktrc:1c,2" "-ktrf:1" "-o" messages-pot-path js-path) + (doseq [po po-files-to-merge] + (when (.exists (io/as-file (po-path settings po))) + (lmain/info "Merging new template to existing translations for " po) + (util/run "msgmerge" "--force-po" "--no-wrap" "-U" (po-path settings po) messages-pot-path))))) (defn deploy-translations "This subtask converts translated .po files into locale-specific .cljs files for runtime string translation." [project] - (let [replace-hyphen #(str/replace % #"-" "_") - trans-ns (util/translation-namespace project) - src-base (or (-> project :untangled-i18n :source-folder) "src") - output-dir (util/cljs-output-dir src-base trans-ns) - po-files (util/find-po-files msgs-dir-path) - default-lc (util/default-locale project) + (let [explicit-settings (get project :untangled-i18n {}) + builds (get-in project [:cljsbuild :builds]) + settings-with-defaults (merge i18n-defaults explicit-settings) + settings (check-settings! settings-with-defaults builds) + replace-hyphen #(str/replace % #"-" "_") + trans-ns (:translation-namespace settings) + output-dir (:translation-target settings) + po-files (util/find-po-files (:po-files settings)) + default-lc (:default-locale settings) locales (map util/clojure-ize-locale po-files) locales-inc-default (conj locales default-lc) default-lc-translation-path (str output-dir "/" (replace-hyphen default-lc) ".cljs") default-lc-translations (cg/wrap-with-swap :namespace trans-ns :locale default-lc :translation {}) - locales-code-string (cg/gen-locales-ns project locales) + locales-code-string (cg/gen-locales-ns settings locales) locales-path (str output-dir "/locales.cljs") default-locale-code-string (cg/gen-default-locale-ns trans-ns default-lc) default-locale-path (str output-dir "/default_locale.cljs")] - (sh "mkdir" "-p" output-dir) + (cg/write-cljs-translation-file default-locale-path default-locale-code-string) (if (some #{default-lc} locales) (cg/write-cljs-translation-file locales-path locales-code-string) @@ -96,54 +137,21 @@ (doseq [po po-files] (let [locale (util/clojure-ize-locale po) - translation-map (parse/map-translations (po-path po)) + translation-map (parse/map-translations (po-path settings po)) cljs-translations (cg/wrap-with-swap :namespace trans-ns :locale locale :translation translation-map) cljs-trans-path (str output-dir "/" (replace-hyphen locale) ".cljs")] (cg/write-cljs-translation-file cljs-trans-path cljs-translations))) - (lmain/warn "Deployed translations for the following locales:" locales) - - (if-let [modules-map (lookup-modules project locales-inc-default)] - (do (lmain/warn - " - No :modules configuration detected for dynamically loading translations! - Your production cljsbuild should look something like this: - ") - (lmain/warn (pp/write modules-map :stream nil) - " - "))))) - -(defn extract-strings - "This subtask extracts strings from your cljs files that should be translated." - [project] - (if (util/gettext-missing?) - (puke "The xgettext and msgcat commands are not installed, or not on your $PATH.") - (if (util/dir-missing? msgs-dir-path) - (puke "The i18n/msgs directory is missing in your project! Please create it.") - (let [cljsbuilds-path [:cljsbuild :builds] - builds (get-in project cljsbuilds-path) - cljs-prod-build (util/get-cljsbuild builds (util/target-build project)) - i18n-exiting-build (util/get-cljsbuild builds "i18n") - i18n-build (if i18n-exiting-build i18n-exiting-build (configure-i18n-build cljs-prod-build)) - i18n-project (assoc-in project cljsbuilds-path [i18n-build]) - build-path (-> i18n-build :compiler :output-to) - po-files-to-merge (util/find-po-files msgs-dir-path) - cmd-args (list "xgettext" "--from-code=UTF-8" "--debug" "-k" "-ktr:1" "-ktrc:1c,2" "-ktrf:1" "-o" messages-pot-path build-path) - build-result (cljsbuild i18n-project "once" "i18n") - _ (lmain/info (str "Build result: " build-result) (str "Running: " (string/join " " cmd-args))) - sh-result (apply sh cmd-args) - _ (lmain/info (str "Extract result: " sh-result))] - (doseq [po po-files-to-merge] - (sh "msgcat" "--no-wrap" messages-pot-path (po-path po) "-o" (po-path po))))))) + (lmain/info "Deployed translations for the following locales:" locales))) (defn i18n "A plugin which automates your i18n string translation workflow" {:subtasks [#'extract-strings #'deploy-translations]} ([project] - (puke "Bad you!")) + (puke "Usage: lein i18n (extract-strings | deploy-translations)")) ([project subtask] (case subtask "extract-strings" (extract-strings project) "deploy-translations" (deploy-translations project) - (puke (str "Unrecognized subtask: " subtask))))) + (puke "Unrecognized subtask:" subtask "\n Use 'extract-strings' or 'deploy-translations'.")))) diff --git a/src/leiningen/i18n/code_gen.clj b/src/leiningen/i18n/code_gen.clj index dd79ee6..423db53 100644 --- a/src/leiningen/i18n/code_gen.clj +++ b/src/leiningen/i18n/code_gen.clj @@ -1,7 +1,8 @@ (ns leiningen.i18n.code-gen (:require [clojure.string :as str] [clojure.pprint :as pp] - [leiningen.i18n.util :as util])) + [leiningen.i18n.util :as util] + [leiningen.core.main :as lmain])) (defn wrap-with-swap "Wrap a translation map with supporting clojurescript code @@ -27,6 +28,7 @@ (str/join "\n\n" [ns-decl comment trans-def swap-decl goog-module-decl]))) (defn write-cljs-translation-file [fname translations-string] + (lmain/info "Writing " fname) (spit fname translations-string)) (defn gen-default-locale-ns @@ -49,17 +51,17 @@ (defn gen-locales-ns " Generates a code string that assists in dynamically loading translations when a user changes their locale. Uses the - leiningen project map to configure the code string's namespace as well as the output directory for locale modules. + i18n settings to configure the code string's namespace as well as the output directory for locale modules. Parameters: - * `project`: A leiningen project map + * `settings`: A leiningen project map * `locales`: A list of locale strings Returns a string of cljs code." - [project locales] - (let [translation-namespace (-> project util/translation-namespace) + [settings locales] + (let [translation-namespace (:translation-namespace settings) locales-ns (-> translation-namespace (str ".locales") symbol) - translations (map #(symbol ( str translation-namespace "." %)) locales) + translations (map #(symbol (str translation-namespace "." %)) locales) ns-decl (pp/write (list 'ns locales-ns (concat (list :require @@ -68,11 +70,9 @@ '[goog.module.ModuleManager :as module-manager] '[untangled.i18n.core :as i18n] ) - translations - ) + translations) (list :import 'goog.module.ModuleManager)) :stream nil) - output-dir (:output-dir (:compiler (util/get-cljsbuild (get-in project [:cljsbuild :builds]) (util/target-build project)))) - abs-module-path (str/join (interleave (repeat "/") (drop 2 (str/split output-dir #"/")))) + abs-module-path (:module-basepath settings) manager-def (list 'defonce 'manager (list 'module-manager/getInstance)) modules-map (reduce #(assoc %1 %2 (str abs-module-path "/" %2 ".js")) {} locales) modules-def (pp/write (list 'defonce 'modules (symbol (str "#js")) modules-map) :stream nil) @@ -83,15 +83,11 @@ (list '.setLoader 'manager 'loader) (list '.setAllModuleInfo 'manager 'module-info) (list '.setModuleUris 'manager 'modules) - 'loader)) :pretty false :stream nil) - set-locale-def (list 'defn 'set-locale ['l] - (list 'js/console.log (list 'str "LOADING ALTERNATE LOCALE: " 'l)) + 'loader)) :pretty true :stream nil) + set-locale-def (list 'defn (symbol "^:export") 'set-locale ['l] (list 'if (list 'exists? 'js/i18nDevMode) - (list 'do (list 'js/console.log (list 'str "LOADED ALTERNATE LOCALE in dev mode: " 'l)) - (list 'reset! 'i18n/*current-locale* 'l) - ) + (list 'reset! 'i18n/*current-locale* 'l) (list '.execOnLoad 'manager 'l (list 'fn 'after-locale-load [] - (list 'js/console.log (list 'str "LOADED ALTERNATE LOCALE: " 'l)) (list 'reset! 'i18n/*current-locale* 'l)))))] (str/join "\n\n" [ns-decl manager-def modules-def mod-info-def loader-def set-locale-def]))) diff --git a/src/leiningen/i18n/util.clj b/src/leiningen/i18n/util.clj index 4c05725..ab619f6 100644 --- a/src/leiningen/i18n/util.clj +++ b/src/leiningen/i18n/util.clj @@ -1,16 +1,20 @@ (ns leiningen.i18n.util (:require [clojure.java.shell :refer [sh]] - [clojure.string :as str])) + [clojure.string :as str] + [leiningen.cljsbuild :refer [cljsbuild]] + [leiningen.core.main :as lmain]) + (:import (java.io File))) + +(defn puke [& msgs] + (apply lmain/warn msgs) + (lmain/abort)) + +(defn po-path [settings po-file] (.getAbsolutePath (new File (:po-dir settings) po-file))) (defn cljs-output-dir [src-base namespace] (let [path-from-namespace (str/replace (str namespace) #"\." "/")] (str src-base "/" path-from-namespace))) -(defn default-locale [project] - (if-let [locale (get-in project [:untangled-i18n :default-locale])] - locale - "en-US")) - (defn find-po-files [msgs-dir-path] (filter #(.endsWith % ".po") (clojure.string/split-lines @@ -21,24 +25,28 @@ msgcat (:exit (sh "which" "msgcat"))] (or (> xgettext 0) (> msgcat 0)))) -(defn dir-missing? [dir] - (-> (sh "ls" "-d" dir) - (get :exit) - (> 0))) - (defn cljs-build? [build target] (if (= (:id build) target) build false)) +(defn run + "Run a shell command and logging the command and result." + [& args] + (lmain/info "Running: " (str/join " " args)) + (let [result (:exit (apply sh args))] + (when (not= 0 result) + (puke "FAILED!")))) + +(defn build + "Run a cljsbuild with logging output, but die if it fails (with a helpful message)" + [project build-name] + (lmain/info "Running: cljsbuild once " build-name) + (cljsbuild project "once" build-name)) + (defn get-cljsbuild [builds target] (some #(cljs-build? % target) builds)) -(defn translation-namespace [project] - (if-let [ns (get-in project [:untangled-i18n :translation-namespace])] - ns - (symbol 'untangled.translations))) - (defn clojure-ize-locale [po-filename] (-> po-filename (str/replace #"^([a-z]+_*[A-Z]*).po$" "$1") @@ -47,5 +55,14 @@ (defn target-build [project] (if-let [target (get-in project [:untangled-i18n :target-build])] target - "production")) + (do + (lmain/warn "No production target build specified! Assuming 'production'") + "production"))) + +(defn target-build [project] + (if-let [target (get-in project [:untangled-i18n :target-build])] + target + (do + (lmain/warn "No production target build specified! Assuming 'production'") + "production"))) From 13f62b1d14cbcac4321b56da41d9ebe1c11e3c26 Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Mon, 21 Nov 2016 13:45:37 -0500 Subject: [PATCH 5/9] dramatically improved module support code generation --- src/leiningen/i18n.clj | 16 ++++++---- src/leiningen/i18n/code_gen.clj | 53 ++++++++++++++++++++------------- src/leiningen/i18n/util.clj | 4 ++- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/leiningen/i18n.clj b/src/leiningen/i18n.clj index 20cfa04..7da4aaf 100644 --- a/src/leiningen/i18n.clj +++ b/src/leiningen/i18n.clj @@ -29,7 +29,7 @@ (defn check-production-build! [nm build] (when (nil? build) (puke "The specified production build (" nm ") does not exist in the project file.")) - (when (nil? (get-in build [:compiler :output-to])) + (when (nil? (get-in build [:compiler :output-dir])) (puke "The production build (" nm ") has no :output-to setting.")) (when-not (#{:whitespace :simple :advanced} (get-in build [:compiler :optimizations])) (puke "The production build (" nm ") should specify optimizations of :whitespace, :simple, or :advanced")) @@ -71,18 +71,22 @@ production-build (util/get-cljsbuild builds prod-build-name) module-basepath (or (-> production-build :compiler :asset-path) "/") trans-ns (:translation-namespace settings) + module-server-path (util/cljs-output-dir module-basepath trans-ns) src-base (:source-folder settings) output-dir (util/cljs-output-dir src-base trans-ns) outdir ^File (io/as-file output-dir) + translation-modules (->> production-build :compiler :modules keys (map name) set) updated-settings (merge settings - {:translation-target output-dir - :module-basepath module-basepath - :translation-js (get-in i18n-build [:compiler :output-to])} + {:translation-target output-dir + :translation-modules translation-modules + :module-server-path module-server-path + :translation-js (get-in i18n-build [:compiler :output-to])} (setup-environment! settings))] (when (not (.exists outdir)) (lmain/info "Making missing source folder " outdir) (.mkdirs outdir)) - (lmain/info "Locale modules will be build to load relative to asset path: " module-basepath) + (lmain/info "The following locales will be written as loadable modules: " translation-modules) + (lmain/info "Locale modules (if used) will be built to load from the server path: " module-server-path) (lmain/info "Translation build: " i18n-build-name) (check-i18n-build! i18n-build-name i18n-build) (check-production-build! prod-build-name production-build) @@ -130,7 +134,7 @@ (cg/write-cljs-translation-file default-locale-path default-locale-code-string) (if (some #{default-lc} locales) (cg/write-cljs-translation-file locales-path locales-code-string) - (let [locales-code-string (cg/gen-locales-ns project locales-inc-default)] + (let [locales-code-string (cg/gen-locales-ns settings locales-inc-default)] (cg/write-cljs-translation-file locales-path locales-code-string) (cg/write-cljs-translation-file default-lc-translation-path default-lc-translations))) (lmain/warn "Configured project for default locale:" default-lc) diff --git a/src/leiningen/i18n/code_gen.clj b/src/leiningen/i18n/code_gen.clj index 423db53..d7b1000 100644 --- a/src/leiningen/i18n/code_gen.clj +++ b/src/leiningen/i18n/code_gen.clj @@ -22,9 +22,11 @@ trans-def (pp/write (list 'def 'translations translation) :stream nil) swap-decl (pp/write (list 'swap! 'untangled.i18n.core/*loaded-translations* (list 'fn '[x] (list 'assoc 'x locale 'translations))) :stream nil) - goog-module-decl (pp/write (list 'if (list 'exists? 'js/i18nDevMode) - :noop - (list '-> 'goog.module.ModuleManager '.getInstance (list '.setLoaded locale))) :stream nil)] + goog-module-decl (pp/write '(try + (-> goog.module.ModuleManager + .getInstance + (.setLoaded locale)) + (catch js/Object e)) :stream nil)] (str/join "\n\n" [ns-decl comment trans-def swap-decl goog-module-decl]))) (defn write-cljs-translation-file [fname translations-string] @@ -61,33 +63,44 @@ [settings locales] (let [translation-namespace (:translation-namespace settings) locales-ns (-> translation-namespace (str ".locales") symbol) - translations (map #(symbol (str translation-namespace "." %)) locales) + xns (fn [locale] (symbol (str translation-namespace "." locale))) + locale-modules (:translation-modules settings) + supports-modules? (boolean (seq locale-modules)) + is-module? (fn [l] (contains? locale-modules l)) + translations-to-include (reduce (fn [acc l] + (if (is-module? l) acc (conj acc (xns l)))) + #{(xns (:default-locale settings))} locales) ns-decl (pp/write (list 'ns locales-ns (concat (list :require 'goog.module 'goog.module.ModuleLoader '[goog.module.ModuleManager :as module-manager] - '[untangled.i18n.core :as i18n] - ) - translations) + '[untangled.i18n.core :as i18n]) + translations-to-include) (list :import 'goog.module.ModuleManager)) :stream nil) - abs-module-path (:module-basepath settings) + abs-module-path (:module-server-path settings) manager-def (list 'defonce 'manager (list 'module-manager/getInstance)) modules-map (reduce #(assoc %1 %2 (str abs-module-path "/" %2 ".js")) {} locales) modules-def (pp/write (list 'defonce 'modules (symbol (str "#js")) modules-map) :stream nil) mod-info-map (reduce #(assoc %1 %2 []) {} locales) mod-info-def (list 'defonce 'module-info (symbol (str "#js")) mod-info-map) loader-def (pp/write (list 'defonce (symbol "^:export") - 'loader (list 'let ['loader (list 'goog.module.ModuleLoader.)] - (list '.setLoader 'manager 'loader) - (list '.setAllModuleInfo 'manager 'module-info) - (list '.setModuleUris 'manager 'modules) - 'loader)) :pretty true :stream nil) - set-locale-def (list 'defn (symbol "^:export") 'set-locale ['l] - (list 'if (list 'exists? 'js/i18nDevMode) - (list 'reset! 'i18n/*current-locale* 'l) - (list '.execOnLoad 'manager 'l - (list 'fn 'after-locale-load [] - (list 'reset! 'i18n/*current-locale* 'l)))))] - (str/join "\n\n" [ns-decl manager-def modules-def mod-info-def loader-def set-locale-def]))) + 'loader '(let [loader (goog.module.ModuleLoader.)] + (.setLoader manager loader) + (.setAllModuleInfo manager module-info) + (.setModuleUris manager modules) + loader)) :stream nil) + set-locale-def (if supports-modules? + (list 'defn (symbol "^:export") 'set-locale '[l] + '(reset! i18n/*current-locale* l) + '(try + (.execOnLoad manager l + (fn after-locale-load [] + (reset! i18n/*current-locale* l))) + (catch js/Object e))) + (list 'defn (symbol "^:export") 'set-locale '[l] '(reset! i18n/*current-locale* l)))] + (str/join "\n\n" + (if supports-modules? + [ns-decl manager-def modules-def mod-info-def loader-def set-locale-def] + [ns-decl set-locale-def])))) diff --git a/src/leiningen/i18n/util.clj b/src/leiningen/i18n/util.clj index ab619f6..7cc0cdd 100644 --- a/src/leiningen/i18n/util.clj +++ b/src/leiningen/i18n/util.clj @@ -12,7 +12,9 @@ (defn po-path [settings po-file] (.getAbsolutePath (new File (:po-dir settings) po-file))) (defn cljs-output-dir [src-base namespace] - (let [path-from-namespace (str/replace (str namespace) #"\." "/")] + (let [path-from-namespace (-> (str namespace) + (str/replace #"\." "/") + (str/replace #"-" "_"))] (str src-base "/" path-from-namespace))) (defn find-po-files [msgs-dir-path] From f04b1a68cc043fe3d8a57281370431a70e399337 Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Mon, 21 Nov 2016 13:47:40 -0500 Subject: [PATCH 6/9] removed spec. Does not make much sense on a plugin that is largely side-effecting --- spec/leiningen/i18n/code_gen_spec.clj | 98 --------------------- spec/leiningen/i18n/parse_po_spec.clj | 118 -------------------------- spec/leiningen/i18n/util_spec.clj | 86 ------------------- spec/leiningen/i18n_spec.clj | 68 --------------- spec/leiningen/i18n_spec_fixtures.clj | 80 ----------------- 5 files changed, 450 deletions(-) delete mode 100644 spec/leiningen/i18n/code_gen_spec.clj delete mode 100644 spec/leiningen/i18n/parse_po_spec.clj delete mode 100644 spec/leiningen/i18n/util_spec.clj delete mode 100644 spec/leiningen/i18n_spec.clj delete mode 100644 spec/leiningen/i18n_spec_fixtures.clj diff --git a/spec/leiningen/i18n/code_gen_spec.clj b/spec/leiningen/i18n/code_gen_spec.clj deleted file mode 100644 index 8c91ba6..0000000 --- a/spec/leiningen/i18n/code_gen_spec.clj +++ /dev/null @@ -1,98 +0,0 @@ -(ns leiningen.i18n.code-gen-spec - (:require [leiningen.i18n.code-gen :as u] - [leiningen.i18n.util :as util] - [clojure.test :refer (is deftest run-tests testing do-report)] - [untangled-spec.core :refer (specification behavior provided assertions)] - )) - -(specification "the wrap-with-swap function emits a code string" - (let [code-string (u/wrap-with-swap :namespace 'i18n :locale "fr-CA" :translation "{\"fizz\" \"buzz\"}") - import-re #"(?ms).*\(:import.*(goog.module.ModuleManager).*" - ns-re #"(?ms)^(\(ns i18n.fr-CA).*" - import-match (last (re-matches import-re code-string)) - ns-match (last (re-matches ns-re code-string))] - (behavior "that begins with a namespace delcaration" - (assertions - ns-match => "(ns i18n.fr-CA")) - (behavior "which also imports goog's ModuleManager." - (assertions - import-match => "goog.module.ModuleManager"))) - - (let [code-string (u/wrap-with-swap :locale "fr-CA" :translation "{\"fizz\" \"buzz\"}") - module-re #"(?ms).*(\(-> goog.module.ModuleManager \.getInstance \(\.setLoaded \"fr-CA\"\)\)).*" - module-match (last (re-matches module-re code-string)) - atom-re #"(?ms)^.*(untangled.i18n.core/\*loaded-translations\*).*" - atom-match (last (re-matches atom-re code-string))] - (behavior "that ends with a default :atom-name" - (assertions - atom-match => "untangled.i18n.core/*loaded-translations*")) - (behavior "and also a call to ModuleManager.setLoaded" - (assertions - module-match => "(-> goog.module.ModuleManager .getInstance (.setLoaded \"fr-CA\"))")))) - -(specification - "the gen-locales-ns function" - (provided - "when given a project file, emits a code string" - (util/translation-namespace project) => "survey.i18n" - (util/get-cljsbuild whatever target) => {:compiler {:output-dir "res/pub/js/compiled/out"}} - (let [code-string (u/gen-locales-ns {} '("es-MX" "fc-KY") )] - (behavior - "that begins with configurable namespace declaration" - (assertions - (last (re-matches #"(?ms)^\((ns\n survey.i18n.locales).*" code-string)) => "ns\n survey.i18n.locales")) - - (behavior - "that contains a javascript map of locales to corresponding .js files in the output directory" - (assertions - (last (re-matches #"(?ms).*(\"fc-KY\" \"/js/compiled/out/fc-KY.js\").*" - code-string)) => "\"fc-KY\" \"/js/compiled/out/fc-KY.js\"") - (behavior - "which is then def-once'd to the modules symbol" - (assertions - (last (re-matches #"(?ms).*(\(defonce\n modules\n #js).*" code-string)) => "(defonce\n modules\n #js"))) - - (behavior - "that contains a javascript map of locales to an empty vector" - (assertions - (last (re-matches #"(?ms).*(\{\"es-MX\" \[\], \"fc-KY\" \[\]\}).*" - code-string)) => "{\"es-MX\" [], \"fc-KY\" []}") - (behavior - "which is then def-once'd to the module-info symbol" - (assertions - (last (re-matches #"(?ms).*(\(defonce module-info #js).*" - code-string)) => "(defonce module-info #js"))) - - (behavior "that creates a defonce with ^:export annotation for goog's ModuleLoader" - (assertions - (last (re-matches #"(?ms).*(\(defonce \^:export loader \().*" code-string)) => "(defonce ^:export loader (")) - (behavior "that defines a set-locale function" - (assertions - (last (re-matches #"(?ms).*(\(defn set-locale).*" code-string)) => "(defn set-locale"))))) - -(specification - "the gen-default-locale-ns function" - (behavior - "when given a namespace and a default locale, emits a code string" - (let [code-string (u/gen-default-locale-ns 'survey.i18n "fc-KY")] - - (behavior - "that begins with a configurable namespace declaration" - (assertions - (last (re-matches #"(?ms)^(\(ns survey\.i18n\.default-locale).*" code-string)) => "(ns survey.i18n.default-locale") - - (behavior "which contains a :require of the default locale translation file" - (assertions - (last - (re-matches #"(?ms).*(\(:require survey\.i18n\.fc-KY).*" code-string)) => "(:require survey.i18n.fc-KY"))) - - (behavior "that contains a reset on the *current-locale* atom" - (assertions - (last - (re-matches #"(?ms).*\n(\(reset!.*\"fc-KY\"\)).*" code-string)) => "(reset! i18n/*current-locale* \"fc-KY\")")) - - (behavior "that contains a swap on the *loaded-translations* atom" - (assertions - (last - (re-matches #"(?ms).*(\(swap!.*translations\)\)).*" - code-string)) => "(swap! i18n/*loaded-translations* #(assoc % :fc-KY survey.i18n.fc-KY/translations))"))))) diff --git a/spec/leiningen/i18n/parse_po_spec.clj b/spec/leiningen/i18n/parse_po_spec.clj deleted file mode 100644 index ba2cb6c..0000000 --- a/spec/leiningen/i18n/parse_po_spec.clj +++ /dev/null @@ -1,118 +0,0 @@ -(ns leiningen.i18n.parse-po-spec - (:require [clojure.test :refer (is deftest run-tests testing do-report)] - [untangled-spec.core :refer (specification behavior provided assertions)] - [leiningen.i18n.parse-po :as u])) - -(def po-file-with-embedded-newlines "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is) distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR , YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-09-24 14:28-0700\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME \\n\"\n\"Language-Team: LANGUAGE \\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=CHARSET\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: i18n/out/compiled.js:26732\nmsgctxt \"context for a multiline xlation\"\nmsgid \"\"\n\"line one\\n\"\n\"two\\n\"\n\"three\"\nmsgstr \"\"\n\"lina uno\\n\"\n\"dos\\n\"\n\"tres\"\n\n#: i18n/out/compiled.js:26732\nmsgid \"\"\n\"Select a language\\n\"\n\" to use\\n\"\n\"maybe\"\nmsgstr \"\"\n\"some xlated line\\n\"\n\" por uso\\n\"\n\"que?\"\n") -(def malformed-po-file "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is) distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR , YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-09-24 14:28-0700\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME \\n\"\n\"Language-Team: LANGUAGE \\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=CHARSET\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n#: i18n/out/compiled.js:26732\nmsgctxt \"context for a multi~ xlation\"\nmsgid \"\"\n\"line one\\n\"\n\"two\\n\"\n\"three\"\nmsgstr \"\"\n\"lina uno\\n\"\n\"dos\\n\"\n\"tres\"\n#: i18n/out/compiled.js:26732\nmsgid \"\"\n\"Select a language\\n\"\n\" to use\\n\"\n\"maybe\"\nmsgstr \"\"\n\"some xlated line\\n\"\n\" por uso\\n\"\n\"que?\"\n") -(def po-file "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR , YEAR.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: \\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2015-09-15 15:24-0700\\n\"\n\"PO-Revision-Date: 2015-09-15 15:30-0700\\n\"\n\"Language-Team: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: Poedit 1.8.4\\n\"\n\"Last-Translator: \\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"Language: es_MX\\n\"\n\n#: i18n/survey.js:26344\nmsgid \"A sub-component with local state.\"\nmsgstr \"Un subcomponente de estado local.\"\n\n#: i18n/survey.js:26345\nmsgid \"Change my mood...\"\nmsgstr \"Cambiar mi estado de ánimo…\"\n\n#: i18n/survey.js:26345\nmsgid \"Happy!\"\nmsgstr \"¡Feliz!\"\n\n#: i18n/survey.js:26346\nmsgid \"Sad :(\"\nmsgstr \"Triste :(\"\n\n#: i18n/survey.js:26354\nmsgctxt \"abbreviation for male gender\"\nmsgid \"M\"\nmsgstr \"H\"\n\n#: i18n/survey.js:26355\nmsgid \"A button with a click count: \"\nmsgstr \"Un botón con un clic la cuenta:\"\n\n#: i18n/survey.js:26355\nmsgid \"Click me\"\nmsgstr \"Clic aquí\"\n\n#: i18n/survey.js:26356\nmsgid \"An input that is two-way bound:\"\nmsgstr \"Límite de una entrada que es de dos vía:\"\n\n#: i18n/survey.js:26358\nmsgid \"Sub component below: ({swings, number} mood swings so far)\"\nmsgstr \"Componente de sub abajo: ({columpios, número} hasta el momento de ánimo)\"\n") - - - -(specification "the map-translations funtion" - (provided "when a translation contains single-line translations" - (slurp some-file) =1x=> po-file - (let [translations (u/map-translations "wat") - xlation-with-ctxt (get translations "abbreviation for male gender|M") - xlation-without-ctxt (get translations "|A sub-component with local state.")] - (behavior "stores the translation without context" - (assertions - xlation-without-ctxt => "Un subcomponente de estado local.")) - (behavior "stores the translation with context" - (assertions - xlation-with-ctxt => "H")))) - (provided "when a translation contains embedded newlines" - (slurp some-file) =1x=> po-file-with-embedded-newlines - (let [translations (u/map-translations "wat") - mutliline-xlation (get translations "|Select a language\n to use\nmaybe")] - (behavior "stores multiline values" - (assertions - mutliline-xlation => "some xlated line\n por uso\nque?")) - (behavior "stores values at multiline keys" - (assertions - (contains? translations "|Select a language\n to use\nmaybe") => true))))) - -(specification "the join-quoted-strings function" - (let [string ["msgctxt \"context for a multiline xlation\""] - strings ["msgid \"\"" "\"line one\n\"" "\"two\n\"" "\"three\""]] - (behavior "returns unquoted string from single string" - (assertions - (u/join-quoted-strings string) => "context for a multiline xlation")) - (behavior "returns unquoted string from a vector of quoted strings" - (assertions - (u/join-quoted-strings strings) => "line one\ntwo\nthree")))) - -(specification "the map-translation-components function" - (let [grouped-chunk [["msgctxt \"context for a multiline xlation\""] - ["msgid \"\"" "\"line one\n\"" "\"two\n\"" "\"three\""] - ["msgstr \"\"" "\"lina uno\n\"" "\"dos\n\"" "\"tres\""]] - mapped-translation (u/map-translation-components {} grouped-chunk)] - (behavior "keys content by :msgid, :msgctxt and :msgstr" - (assertions - (contains? mapped-translation :msgid) => true - (contains? mapped-translation :msgctxt) => true - (contains? mapped-translation :msgstr) => true)) - (behavior "collapses multiline subcomponent into a single value" - (assertions - (:msgctxt mapped-translation) => "context for a multiline xlation" - (:msgid mapped-translation) => "line one\ntwo\nthree" - (:msgstr mapped-translation) => "lina uno\ndos\ntres")))) - -(specification "the group-translations function" - (provided "when grouping translations" - (slurp some-file) =2x=> po-file - - (behavior "begins groups with msgid or msgctxt" - (assertions - (-> "some fname" u/group-translations (nth 4) first first (subs 0 7)) => "msgctxt" - (-> "some fname" u/group-translations first first first (subs 0 5)) => "msgid"))) - - (provided "when given a malformed po file string" - (slurp some-file) =1x=> malformed-po-file - - (behavior "returns nil" - (assertions - (u/group-translations "some fname") => nil))) - - (provided "when given a multi-line translation" - (slurp some-file) =1x=> po-file-with-embedded-newlines - (behavior "returns groups of translations" - (assertions - (count (first (u/group-translations "some fname"))) => 3)))) - - -(specification "the group-chunks function" - (behavior "when given an ungrouped translation chunk" - (let [multiln-with-ctxt '("msgctxt \"context for a multiline xlation\"" - "msgid \"\"" - "\"line one\\n\"" - "\"two\\n\"" - "\"three\"" - "msgstr \"\"" - "\"lina uno\\n\"" - "\"dos\\n\"" - "\"tres\"") - multiln-without-ctxt '("msgid \"\"" - "\"line one\\n\"" - "\"two\\n\"" - "\"three\"" - "msgstr \"\"" - "\"lina uno\\n\"" - "\"dos\\n\"" - "\"tres\"") - grouped-with-ctxt (u/group-chunks multiln-with-ctxt) - grouped-without-ctxt (u/group-chunks multiln-without-ctxt)] - (behavior "removes extra \\ escape from embedded newlines" - (assertions - (-> grouped-without-ctxt first second) => "\"line one\n\"")) - (behavior "ends groups with msgstr" - (assertions - (-> grouped-without-ctxt reverse first first (subs 0 6)) => "msgstr" - (-> grouped-with-ctxt reverse first first (subs 0 6)) => "msgstr")) - (behavior "begins groups with msgid or msgctxt" - (assertions - (-> grouped-without-ctxt first first (subs 0 5)) => "msgid" - (-> grouped-with-ctxt first first (subs 0 7)) => "msgctxt"))))) - - - diff --git a/spec/leiningen/i18n/util_spec.clj b/spec/leiningen/i18n/util_spec.clj deleted file mode 100644 index 2f96fed..0000000 --- a/spec/leiningen/i18n/util_spec.clj +++ /dev/null @@ -1,86 +0,0 @@ -(ns leiningen.i18n.util-spec - (:require [leiningen.i18n-spec-fixtures :as fixture] - [leiningen.i18n.util :as e] - [clojure.test :refer (is deftest run-tests testing do-report)] - [clojure.java.shell :refer [sh]] - [untangled-spec.core :refer (specification behavior provided assertions)])) - -(let [which "which" - xg "xgettext" - mc "msgcat" - ls "ls" - dir "dir" - po-dir "de.po\nfoofah.txt\ntldr.md\nja_JP.po"] - - (specification "the default-locale function" - (behavior "returns the default-locale configured in the project" - (assertions - (e/default-locale - {:untangled-i18n {:default-locale "es-MX"}}) => "es-MX")) - - (behavior "defaults to untangled.translations" - (assertions - (e/default-locale {}) => "en-US"))) - - (specification "the translation-namespace function" - (behavior "returns the namespace configured in the project" - (assertions - (e/translation-namespace - {:untangled-i18n {:translation-namespace 'i18n}}) => 'i18n)) - - (behavior "defaults to untangled.translations" - (assertions - (e/translation-namespace {}) => 'untangled.translations))) - - (specification "the find-po-files function" - (provided "when no files are found" - (sh ls dir) =1x=> {:out ""} - (behavior "returns an empty list" - (assertions - (e/find-po-files dir) => '()))) - - (provided "when some po files are found among other files" - (sh ls dir) =1x=> {:out po-dir} - (behavior "returns a list of the po files" - (assertions - (e/find-po-files dir) => '("de.po" "ja_JP.po"))))) - - - (specification "the gettext-missing? function" - (provided "when xgettext and msgcat are installed" - (sh which xg) =1x=> {:exit 0} - (sh which mc) =1x=> {:exit 0} - (behavior "returns false" - (assertions (e/gettext-missing?) => false))) - - (provided "when xgettext and msgcat are not installed" - (sh which xg) =1x=> {:exit 1} - (sh which mc) =1x=> {:exit 1} - (behavior "returns true" - (assertions (e/gettext-missing?) => true))) - - (provided "when xgettext is installed, but msgcat is not" - (sh which xg) =1x=> {:exit 0} - (sh which mc) =1x=> {:exit 1} - (behavior "returns true" - (assertions (e/gettext-missing?) => true)))) - - (specification "the cljs-output-dir function" - (behavior "returns a path string to the translation-namespace in src" - (assertions - (e/cljs-output-dir "src" 'i18n) => "src/i18n" - (e/cljs-output-dir "src" 'i18n.some.more-namespace) => "src/i18n/some/more-namespace")))) - -(specification "the cljsbuild-prod-build? function" - (behavior "returns false if :id is not \"production\"" - (assertions - (e/cljs-build? fixture/dev-build (e/target-build "x")) => false)) - - (behavior "returns a build with :id \"production\"" - (assertions - (e/cljs-build? fixture/prod-build (e/target-build "production")) => fixture/prod-build))) - -(specification "the get-cljsbuild function" - (behavior "returns a production build" - (assertions - (e/get-cljsbuild fixture/cljs-builds (e/target-build "production")) => fixture/prod-build))) diff --git a/spec/leiningen/i18n_spec.clj b/spec/leiningen/i18n_spec.clj deleted file mode 100644 index 88ae0a1..0000000 --- a/spec/leiningen/i18n_spec.clj +++ /dev/null @@ -1,68 +0,0 @@ -(ns leiningen.i18n-spec - (:require [clojure.test :refer (is deftest run-tests testing do-report)] - [untangled-spec.core :refer (specification behavior provided assertions)] - [leiningen.i18n-spec-fixtures :as fixture] - - [leiningen.i18n :as e])) - - - - -(specification "the configure-i18n-build function" - (let [i18n-build (e/configure-i18n-build fixture/prod-build)] - (behavior "assigns :id \"i18n\" to the build" - (assertions - (:id i18n-build) => "i18n")) - - (behavior "enables :optimizations :whitespace on the build" - (assertions - (get-in i18n-build [:compiler :optimizations]) => :whitespace)))) - - - -(specification - "the lookup-modules function" - (let [proj-with-modules {:cljsbuild {:builds [{:id "production" - :compiler {:modules {}}}]}} - - proj-without-modules {:name "survey" - :cljsbuild {:builds [{:id "production" - :compiler {:main "survey.main" - :output-dir "res/pub/js/comp/out"}}]} - :untangled-i18n {:translation-namespace 'survey.i18n - :default-locale "fc-KY"}}] - - (behavior - "when build does not contain modules" - (behavior - "adds :optimizations :advanced to the :compiler" - (assertions - (-> (e/lookup-modules proj-without-modules '()) :compiler :optimizations) => :advanced)) - - (behavior - "removes the top-level :main" - (assertions - (contains? (:compiler (e/lookup-modules proj-without-modules '())) :main) => false)) - - (behavior - "returns a suggested :main module in the map" - (assertions - (-> - (e/lookup-modules - proj-without-modules '()) :compiler :modules :main) => {:output-to "res/pub/js/comp/out/survey.js" - :entries #{"survey.main"}})) - (behavior - "returns locale modules in the map" - (assertions - (-> - (e/lookup-modules - proj-without-modules '("en-US" "fc-KY")) :compiler :modules :fc-KY) => {:output-to "res/pub/js/comp/out/fc-KY.js" - :entries #{"survey.i18n.fc-KY"}}))) - (behavior - "when build contains modules" - (behavior - "returns nil" - (assertions - (e/lookup-modules proj-with-modules '()) => nil))))) - - diff --git a/spec/leiningen/i18n_spec_fixtures.clj b/spec/leiningen/i18n_spec_fixtures.clj deleted file mode 100644 index b99a25a..0000000 --- a/spec/leiningen/i18n_spec_fixtures.clj +++ /dev/null @@ -1,80 +0,0 @@ -(ns leiningen.i18n-spec-fixtures) - -(def dev-build - {:source-paths ["src"], - :id "dev", - :compiler - {:output-dir "resources/public/js/compiled/prod", - :optimizations :advanced, - :output-to "resources/public/js/compiled/survey.js", - :asset-path "js/compiled/prod", - :main 'survey.main}}) - -(def prod-build - {:source-paths ["src"], - :id "production", - :compiler - {:output-dir "resources/public/js/compiled/prod", - :optimizations :advanced, - :output-to "resources/public/js/compiled/survey.js", - :asset-path "js/compiled/prod", - :main 'survey.main}}) - -(def cljs-builds - '[{:source-paths - ["src" - "dev" - "checkouts/untangled/src" - "checkouts/untangled-spec/src"], - :figwheel {:on-jsload "cljs.user/reload"}, - :id "dev", - :compiler - {:output-dir "resources/public/js/compiled/dev", - :optimizations :none, - :recompile-dependents true, - :output-to "resources/public/js/compiled/survey.js", - :source-map-timestamp true, - :asset-path "js/compiled/dev", - :main cljs.user}} - {:source-paths - ["specs" - "src" - "checkouts/untangled/src" - "checkouts/untangled-spec/src"], - :figwheel {:on-jsload "survey.user/run-tests"}, - :id "test", - :compiler - {:output-dir "resources/public/js/compiled/specs", - :optimizations :none, - :recompile-dependents true, - :output-to "resources/public/js/specs/specs.js", - :asset-path "js/compiled/specs", - :main survey.user}} - {:source-paths - ["src" "checkouts/untangled/src" "checkouts/untangled-spec/src"], - :figwheel true, - :id "support", - :compiler - {:output-dir "resources/public/js/compiled/viewer", - :optimizations :none, - :recompile-dependents true, - :output-to "resources/public/js/viewer.js", - :asset-path "js/compiled/viewer", - :main survey.viewer}} - {:source-paths ["src"], - :id "production", - :compiler - {:output-dir "resources/public/js/compiled/prod", - :optimizations :advanced, - :output-to "resources/public/js/compiled/survey.js", - :asset-path "js/compiled/prod", - :main survey.main}} - {:source-paths ["src"], - :id "i18n", - :compiler - {:output-dir "i18n/out", - :optimizations :whitespace, - :output-to "i18n/survey.js", - :asset-path "js/compiled/out", - :main survey.main}}]) - From 0adc25b9f86ee6cda3cd76212a6039b55e9b36f2 Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Mon, 21 Nov 2016 14:35:14 -0500 Subject: [PATCH 7/9] updated readme --- README.md | 112 +++++++++++++++++++++++------------------------------- 1 file changed, 47 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 2d65893..cf1c313 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # Untangled Internationalization + + +Release: +Snapshot: + WARNING: This plugin is in progress. It mostly works, but the following bits need a bit of work: - Safari support requires a polyfill -- Dev mode support is fine, but production mode isn't ready -- Module support is almost ready, but needs tuning - The internationalization support in Untangled is based on a number of tools to give a fully-functional, bi-directional localization and internationalization solution that includes: @@ -102,29 +104,44 @@ Also, make sure that the project you are going to leverage the gettext against h ### Configure Plugin -Add `[navis/untangled-lein-i18n "0.1.2"]` to the `:plugins` list in `project.clj`. +Add `[navis/untangled-lein-i18n "0.2.0"]` to the `:plugins` list in `project.clj`. The i18n plugin will look for configuration options at the `:untangled-i18n` key in your `project.clj`: - :untangled-i18n {:default-locale "en-US" - :translation-namespace survey.i18n - :source-folder "src/client" - :target-build "prod" - } - -`:default-locale` is the locale you would like your users to see initially, defaults to `"en-US"` - -`:source-folder` is the location that the plugin should write the `cljs` files during `deploy-translations`. It defaults to `src`. - -`:translation-namespace` is the clojure/clojurescript namespace in which the plugin will deploy translations and -supporting code files. The plugin will create a corresponding directory path if one does not exist. A new subdirectory -will be created after each `.` in the namespace. - -So, the above configuration will generate the following files: + :plugins [navis/untangled-lein-i18n \"0.2.0\"] + + :untangled-i18n {:default-locale \"en\" ;; the default locale of your app + :translation-namespace \"app.i18n\" ;; the namespace for generating cljs translations + :source-folder \"src\" ;; the target source folder for generated code + :translation-build \"i18n\" ;; The name of the cljsbuild to compile your code that has tr calls + :po-files \"msgs\" ;; The folder where you want to store gettext files (.po/.pot) + :production-build \"prod\"} ;; The name of your production build + + ; You need to have a build for generating an i18n source file for string extraction, and one for generating the + ; final production application. You cannot use :advanced optimizations for the i18n step, but must at least use :whitespace + ; so you get a single file. See the Developer's Guide for more details on Internationalization configuration. + :cljsbuild {:builds [{:id \"i18n\" + :source-paths [\"src\"] + :compiler {:output-to \"i18n/out/compiled.js\" + :main entry-point + :optimizations :whitespace}} + {:id \"prod\" + :source-paths [\"src\"] + :compiler {:asset-path \"js\" + :output-dir \"resources/public/js\" + :optimizations :advanced + :source-map true + :modules {;; The main program + :cljs-base {:output-to \"resources/public/js/main.js \"} + ;; One entry for each locale + :de {:output-to \"resources/public/js/de.js \" :entries #{\"app.i18n.de \"}} + :es {:output-to \"resources/public/js/es.js \" :entries #{\"app.i18n.es \"}}}}}]}) + +So, the above configuration (when used) will generate the following files: ``` -src/client/ -└── survey +src/ +└── app ├── i18n │   ├── default_locale.cljs ; The default locale translations │   ├── en_US.cljs ; A file per locale (used for module loading of translations) @@ -162,52 +179,17 @@ project: You now should be able to see the new translations in your app! +### Using the Translations -## Dynamic Translation Loading - -Translations are dynamically loaded when the user requests a change to their locale. The leiningen plugin generates -clojurescript code to support this dynamic loading, but there some requirements your project must meet in order to -support this. - -### :require Supporting Namespaces - -Your `main` namespace should `:require` the `default-locale` namespaces which was generated by the -i18n plugin, eg: - - (ns survey.main - (:require [survey.core :as c] - survey.i18n.default-locale - [untangled.i18n.core :as i18n])) - -### Configure :modules in cljsbuild - -Your production cljsbuild configuration should contain a `:modules` entry which configures support for `goog.module` -to dynamically load JS modules on request. The i18n plugin will detect if your project is missing the `:modules` entry, -and print the suggested configuration. Note that the i18n plugin will not check the correctness of an existing `:modules` -config! - -WARNING: Module loading isn't quite done. The main logic is there, but re-render after load needs to be implemented, and Closure advanced optimizations are not working well yet. - -### Static Translation Loading in Dev Mode - -TODO: This can be fixed so this isn't required...just need to make time. - -When developing your project you may want to test in other locales. Make sure your project has the following: - -- set a javascript variable called `i18nDevMode`, before your app `
` is loaded in index.html: - - - -which will bypass the module loading logic (and prevent the resulting runtime errors). - -## Set Locale at Run-time - -In your application, make sure you've following the untangled client instructions on forced re-render (which uses :react-key). - -In your application make a drop-down that can select your supported locales, and transact: +The generated code will include a `locales` namespace. Just require that and use the generated +`set-locale` function (e.g. in your UI). ``` -(om/transact! this '[(app/change-locale { :lang "en-US" })]) -(om/transact! this '[(app/change-locale { :lang "es-MX" })]) +(ns some.ui + (:require [app.i18n.locales :as l])) + ... +(l/set-locale \"es\") ; change the UI locale, possibly triggering a dynamic module load. ``` + +See the Untangled Developer's Guide for more information. From d35e7c3cdbaa704304d3f59b18ee58d05134de50 Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Mon, 21 Nov 2016 14:36:30 -0500 Subject: [PATCH 8/9] added travis file --- .travis.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0791305 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: clojure From 7e745862da1f68eb034e9c90f6cdcdc9748d4002 Mon Sep 17 00:00:00 2001 From: Tony Kay Date: Mon, 21 Nov 2016 14:39:42 -0500 Subject: [PATCH 9/9] version bump --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 6f9076e..a461a3a 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject navis/untangled-lein-i18n "0.2.0-SNAPSHOT" +(defproject navis/untangled-lein-i18n "0.2.0" :description "A plugin for extracting/populating transalations for Untangled" :url "" :license {:name "MIT"