Commit 6237d4ad authored by Andrew Roetker's avatar Andrew Roetker

(PDB-2547) Add i18n scaffolding to PuppetDB

This commit adds i18n support to middleware.clj and the scaffolding to
add i18n support elsewhere in the codebase using
https://github.com/puppetlabs/clj-i18n, this scaffolding includes a hook
into the `compile` task which will update the `.pot` files
automatically.
parent 2d514299
......@@ -48,3 +48,8 @@ checkouts
# RVM files for localised setups
.ruby-gemset
.ruby-version
# clj-i18n
*.class
/resources/locales.clj
mp-*
include dev-resources/Makefile.i18n
# -*- Makefile -*-
# This file was generated by the i18n leiningen plugin
# Do not edit this file; it iwll be overwritten the next time you run
# lein i18n init
#
# The locale in which our messages are written, and for which we therefore
# have messages without any further effort
MESSAGE_LOCALE=en
# The name of the package into which tranlsations will be placed
PACKAGE=puppetlabs.puppetdb
LOCALES=$(basename $(notdir $(wildcard locales/*.po)))
PACKAGE_DIR=$(subst .,/,$(PACKAGE))
BUNDLE_FILES=$(patsubst %,resources/$(PACKAGE_DIR)/Messages_%.class,$(LOCALES) $(MESSAGE_LOCALE))
SRC_FILES=$(shell find src/ -name \*.clj)
i18n: setup update-pot msgfmt
# Update locales/messages.pot
update-pot: locales/messages.pot
locales/messages.pot: $(SRC_FILES)
@tmp=$(mktemp $@.tmp.XXXX); \
find src/ -name \*.clj \
| xgettext --from-code=UTF-8 --language=lisp \
--copyright-holder 'Puppet Labs <docs@puppetlabs.com>' -F \
--package-name "$(PACKAGE)" \
--package-version "$(PACKAGE_VERSION)" \
--msgid-bugs-address "docs@puppetlabs.com" \
-ktrs:1 -ki18n/trs:1 \
-ktru:1 -ki18n/tru:1 \
--add-comments -o $tmp -f -; \
sed -i -e 's/charset=CHARSET/charset=UTF-8/' $tmp; \
sed -i -e 's/POT-Creation-Date: [^\\]*/POT-Creation-Date: /' $tmp; \
if ! diff -q -I POT-Creation-Date $tmp $@ >& /dev/null; then \
mv $tmp $@; \
else \
rm $tmp; \
fi
# Run msgfmt over all .po files to generate Java resource bundles
msgfmt: $(BUNDLE_FILES)
@(printf '{\n :locales #{'; \
printf "\"%s\"" $(MESSAGE_LOCALE); \
for l in $(LOCALES); do printf " \"%s\"" $$l; done; \
printf '}\n'; \
printf ' :package "$(PACKAGE)"\n'; \
printf ' :bundle "$(PACKAGE).Messages"\n}\n') > resources/locales.clj
resources/$(PACKAGE_DIR)/Messages_%.class: locales/%.po
msgfmt --java2 -d resources -r $(PACKAGE).Messages -l $$(basename $< .po) $<
resources/$(PACKAGE_DIR)/Messages_$(MESSAGE_LOCALE).class: locales/messages.pot
msgfmt --java2 -d resources -r $(PACKAGE).Messages -l $(MESSAGE_LOCALE) $<
# Translators use this when they update translations; this copies any
# changes in the pot file into their language-specific po file
locales/%.po: locales/messages.pot
@if [ -f $@ ]; then \
msgmerge -U $@ $< && touch $@; \
else \
touch $@ && \
msginit --no-translator -l $$(basename $@ .po) -o $@ -i $<; \
fi
help:
$(info $(HELP))
@echo
setup:
@mkdir -p locales
.PHONY:setup help
define HELP
This Makefile assists in handling i18n related tasks during development. Files
that need to be checked into source control are put into the locales/ directory.
They are
locales/messages.pot - the POT file generated by 'make update-pot'
locales/$$LANG.po - the translations for $$LANG
Only the $$LANG.po files should be edited manually; this is usually done by
translators.
You can use the following targets:
i18n: refresh all the files in locales/ and recompile resources
update-pot: extract strings and update locales/messages.pot
locales/LANG.po: refresh or create translations for LANG
msgfmt: compile the translations into Java classes; this step is
needed to make translations available to the Clojure code
and produces Java class files in resources/
endef
# @todo lutter 2015-04-20: for projects that use libraries with their own
# translation, we need to combine all their translations into one big po
# file and then run msgfmt over that so that we only have to deal with one
# resource bundle
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Puppet Labs <docs@puppetlabs.com>
# This file is distributed under the same license as the puppetlabs.puppetdb package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: puppetlabs.puppetdb \n"
"Report-Msgid-Bugs-To: docs@puppetlabs.com\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src//puppetlabs/puppetdb/middleware.clj:38
msgid "Processing HTTP request to URI: '{0}'"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:54
msgid "{0} rejected by certificate whitelist {1}"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:55
msgid ""
"The client certificate name {0} doesn't appear in the certificate whitelist. "
"Is your master's (or other PuppetDB client's) certname listed in PuppetDB's "
"certificate-whitelist file?"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:75
msgid "Permission denied: {0}"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:129
msgid "must accept {0}"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:143
msgid "content type {0} not supported"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:159
msgid "Missing required query parameter '{0}'"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:165
msgid "Unsupported query parameter '{0}'"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:268
msgid "Processing command with a content-length of {0} bytes"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:269
msgid ""
"No content length found for POST. POST bodies that are too large could cause "
"memory-related failures."
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:275
msgid ""
"content-length of command is {0} bytes and is larger than the maximum "
"allowed command size of {1} bytes"
msgstr ""
#: src//puppetlabs/puppetdb/middleware.clj:280
msgid "Command rejected due to size exceeding max-command-size"
msgstr ""
......@@ -33,6 +33,7 @@
:pedantic? :abort
:dependencies [[org.clojure/clojure "1.8.0"]
[puppetlabs/i18n "0.2.0"]
[cheshire "5.5.0"]
[org.clojure/core.match "0.3.0-alpha4" :exclusions [org.clojure/tools.analyzer.jvm]]
[org.clojure/math.combinatorics "0.1.1"]
......@@ -99,8 +100,9 @@
:repositories [["releases" "http://nexus.delivery.puppetlabs.net/content/repositories/releases/"]
["snapshots" "http://nexus.delivery.puppetlabs.net/content/repositories/snapshots/"]]
:plugins [[lein-release "1.0.5"]
[lein-cloverage "1.0.6" :exclusions [org.clojure/clojure]]]
:plugins [[lein-release "1.0.5" :exclusions [org.clojure/clojure]]
[lein-cloverage "1.0.6" :exclusions [org.clojure/clojure]]
[puppetlabs/i18n "0.2.0"]]
:lein-release {:scm :git
:deploy-via :lein-deploy}
......
(ns puppetlabs.puppetdb.middleware
"Ring middleware"
(:require [puppetlabs.kitchensink.core :as kitchensink]
(:require [puppetlabs.i18n.core :as i18n]
[puppetlabs.kitchensink.core :as kitchensink]
[puppetlabs.puppetdb.jdbc :as jdbc]
[puppetlabs.puppetdb.query-eng :as qe]
[puppetlabs.puppetdb.utils.metrics :refer [multitime!]]
......@@ -34,7 +35,7 @@
`<logger name=\"puppetlabs.puppetdb.middleware\" level=\"debug\"/>`"
[app]
(fn [req]
(log/debug (str "Processing HTTP request to URI: '" (:uri req) "'"))
(log/debug (i18n/trs "Processing HTTP request to URI: '{0}'" (:uri req)))
(app req)))
(defn build-whitelist-authorizer
......@@ -50,12 +51,9 @@
(if (allowed? req)
:authorized
(do
(log/warnf "%s rejected by certificate whitelist %s" ssl-client-cn whitelist)
(format (str "The client certificate name (%s) doesn't "
"appear in the certificate whitelist. Is your "
"master's (or other PuppetDB client's) certname "
"listed in PuppetDB's certificate-whitelist file?")
ssl-client-cn))))))
(log/warnf (i18n/trs "{0} rejected by certificate whitelist {1}" ssl-client-cn whitelist))
(i18n/tru "The client certificate name {0} doesn't appear in the certificate whitelist. Is your master's (or other PuppetDB client's) certname listed in PuppetDB's certificate-whitelist file?"
ssl-client-cn))))))
(defn wrap-with-authorization
"Ring middleware that will only pass through a request if the
......@@ -74,8 +72,8 @@
(let [auth-result (authorize req)]
(if (= :authorized auth-result)
(app req)
(-> (str "Permission denied: " auth-result)
(rr/response)
(-> (i18n/tru "Permission denied: {0}" auth-result)
rr/response
(rr/status http/status-forbidden))))))))
(defn wrap-with-certificate-cn
......@@ -128,7 +126,7 @@
content-type
(headers "accept"))
(app req)
(rr/status (rr/response (str "must accept " content-type))
(rr/status (rr/response (i18n/tru "must accept {0}" content-type))
http/status-not-acceptable))))
(defn verify-content-type
......@@ -142,15 +140,14 @@
(str (media/base-type content-type)))]
(if (or (nil? mediatype) (some #{mediatype} content-types))
(app req)
(rr/status (rr/response (str "content type " mediatype " not supported"))
(rr/status (rr/response (i18n/tru "content type {0} not supported" mediatype))
http/status-unsupported-type)))))
(defn validate-query-params
"Ring middleware that verifies that the query params in the request
are legal based on the map `param-specs`, which contains a list of
`:required` and `:optional` query parameters. If the validation fails,
a 400 Bad Request is returned, with an explanation of the invalid
parameters."
"Ring middleware that verifies that the query params in the request are legal
based on the map `param-specs`, which contains a list of `:required` and
`:optional` query parameters. If the validation fails, a 400 Bad Request is
returned, with an explanation of the invalid parameters."
[app param-specs]
{:pre [(map? param-specs)
(= #{} (kitchensink/keyset (dissoc param-specs :required :optional)))
......@@ -159,13 +156,13 @@
(fn [{:keys [params] :as req}]
(kitchensink/cond-let [p]
(kitchensink/excludes-some params (:required param-specs))
(http/error-response (str "Missing required query parameter '" p "'"))
(http/error-response (i18n/tru "Missing required query parameter '{0}'" p))
(let [diff (set/difference (kitchensink/keyset params)
(set (:required param-specs))
(set (:optional param-specs)))]
(seq diff))
(http/error-response (str "Unsupported query parameter '" (first p) "'"))
(http/error-response (i18n/tru "Unsupported query parameter '{0}'" (first p)))
:else
(app req))))
......@@ -268,20 +265,19 @@
(let [length-in-bytes (request/content-length req)]
(if length-in-bytes
(log/debugf "Processing command with a content-length of %s bytes" length-in-bytes)
(log/warn (str "No content length found for POST. "
"POST bodies that are too large could cause memory-related failures.")))
(log/debugf (i18n/trs "Processing command with a content-length of {0} bytes") length-in-bytes)
(log/warn (i18n/trs "No content length found for POST. POST bodies that are too large could cause memory-related failures.")))
(if (and length-in-bytes
reject-large-commands?
(> length-in-bytes max-command-size))
(do
(log/warnf "content-length of command is %s bytes and is larger than the maximum allowed command size of %s bytes"
(log/warnf (i18n/trs "content-length of command is {0} bytes and is larger than the maximum allowed command size of {1} bytes")
length-in-bytes
max-command-size)
(consume-and-close (:body req) length-in-bytes)
(http/error-response
"Command rejected due to size exceeding max-command-size"
(i18n/tru "Command rejected due to size exceeding max-command-size")
http/status-entity-too-large))
(app req)))
(app req))))
......@@ -294,7 +290,8 @@
(wrap-with-authorization cert-whitelist)
wrap-with-certificate-cn
wrap-with-default-body
wrap-with-debug-logging))
wrap-with-debug-logging
i18n/locale-negotiator))
(defn parent-check
"Middleware that checks the parent exists before serving the rest of the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment