Skip to content

Commit

Permalink
Merge pull request #16 from reside-ic/reside-71
Browse files Browse the repository at this point in the history
  • Loading branch information
richfitz authored Mar 11, 2021
2 parents 2cc408b + b930c19 commit bc67d1b
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 114 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export(lint_translations)
export(lint_translations_package)
export(lint_translations_report)
export(t_)
export(traduire_options)
export(translator)
export(translator_list)
export(translator_register)
Expand Down
79 changes: 18 additions & 61 deletions R/i18n.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,18 @@
##' resources. If given in this way, then on-demand translation
##' loading (via \code{resource_pattern}) is disabled unless a
##' currently unexposed i18next option is used.
##'
##' @param language The default language for the translation
##'
##' @param default_namespace The default namespace to use. If not
##' given, then \code{i18next} assumes the namespace
##' \code{translation}
##'
##' @param debug Logical, indicating if i18next's debug output should
##' be turned on. This will result in lots of output via
##' \code{message} about various i18next actions.
##'
##' @param resource_pattern A pattern to use for on-demand loading of
##' translation resources. Only works if \code{translations} is
##' \code{NULL} at present.
##'
##' @param namespaces A vector of namespaces to load. Namespaces not
##' listed here may not be loaded as expected (use \code{debug =
##' TRUE} to work out what is going on). The default (\code{NULL})
##' will use i18next's logic, which is to use \code{translation} as
##' the only loaded namespace. This creates some issues if
##' \code{default_namespace} is set here, as the default namespace
##' will not be loaded. A future version of this package will
##' probably do better with the logic here.
##'
##' @param languages A vector of languages to \emph{preload}. You can
##' always add additional languages using the \code{load_language}
##' method. Note that the adding a language here does not (yet)
##' mean that failure to load the language is an error.
##'
##' @param fallback The fallback language to use. The options here
##' are to use a character string (a single fallback to use for all
##' languages), a character vector (a series of languages to use in
##' turn, listed from first to try to last to try) or a named list
##' of language-fallback mappings, e.g., \code{list("de-CH": c("fr",
##' "it"), "es": "fr")}.
##'
##' @param escape Logical, indicating if the translation output should
##' be, by default, escaped (see the i18next interpolation
##' documentation). The i18next implementation is to prevent xss
##' attacks, and so is disabled by default in traduire.
##'
##' @param ... Named options passed to \code{\link{traduire_options}}
##' @param options Options object passed to \code{\link{traduire_options}}
##'
##' @export
##' @examples
##' path <- system.file("examples/simple.json", package = "traduire")
##' obj <- traduire::i18n(path)
##' obj$t("hello", language = "fr")
i18n <- function(resources, language = NULL, default_namespace = NULL,
debug = FALSE, resource_pattern = NULL,
namespaces = NULL, languages = NULL,
fallback = "dev", escape = FALSE) {
## TODO: better defaults for language, but there's lots to consider
## with fallbacks still
R6_i18n$new(resources, language %||% "en", default_namespace,
debug, resource_pattern, namespaces, languages, fallback,
escape)
i18n <- function(resources, ..., options = NULL) {
options <- traduire_options(..., options = options)
R6_i18n$new(resources, options)
}


Expand All @@ -79,20 +36,20 @@ R6_i18n <- R6::R6Class(
),

public = list(
initialize = function(resources, language, default_namespace,
debug, resource_pattern, namespaces, languages,
fallback, escape) {
initialize = function(resources, options) {
resources_js <- read_input(resources)
private$context <- V8::v8()
private$context$source(traduire_file("js/bundle.js"))
private$context$call("init", resources_js, scalar(language),
safe_js_null(default_namespace),
scalar(debug),
safe_js_null(resource_pattern),
namespaces %||% "translation",
safe_js_null(languages),
validate_fallback(fallback),
scalar(escape),
private$context$call("init",
resources_js,
scalar(options[["language"]]),
safe_js_null(options[["default_namespace"]]),
scalar(options[["debug"]]),
safe_js_null(options[["resource_pattern"]]),
options[["namespaces"]],
safe_js_null(options[["languages"]]),
options[["fallback"]],
scalar(options[["escape"]]),
auto_unbox = FALSE)
},

Expand Down Expand Up @@ -236,7 +193,7 @@ i18n_backend_read <- function(pattern, language, namespace) {
## Quite a bit here - if these errors get through to the js, you get
## inscruitable runtime error messages, so we're better off validating
## in R.
validate_fallback <- function(fallback) {
validate_fallback <- function(fallback, name) {
if (is.null(fallback)) {
return(V8::JS("null"))
}
Expand Down
145 changes: 145 additions & 0 deletions R/options.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
##' Create a set of options for traduire
##'
##' Takes a named set of options and optionally an already created
##' options object. If both are set, named options will overwrite
##' any options in the object.
##'
##' These options are passed to i18next.
##'
##' @param ... Named options
##'
##' language The default language for the translation
##'
##' default_namespace The default namespace to use. If not
##' given, then \code{i18next} assumes the namespace
##' \code{translation}
##'
##' debug Logical, indicating if i18next's debug output should
##' be turned on. This will result in lots of output via
##' \code{message} about various i18next actions.
##'
##' resource_pattern A pattern to use for on-demand loading of
##' translation resources. Only works if \code{translations} is
##' \code{NULL} at present.
##'
##' namespaces A vector of namespaces to load. Namespaces not
##' listed here may not be loaded as expected (use \code{debug =
##' TRUE} to work out what is going on). The default (\code{NULL})
##' will use i18next's logic, which is to use \code{translation} as
##' the only loaded namespace. This creates some issues if
##' \code{default_namespace} is set here, as the default namespace
##' will not be loaded. A future version of this package will
##' probably do better with the logic here.
##'
##' languages A vector of languages to \emph{preload}. You can
##' always add additional languages using the \code{load_language}
##' method. Note that the adding a language here does not (yet)
##' mean that failure to load the language is an error.
##'
##' fallback The fallback language to use. The options here
##' are to use a character string (a single fallback to use for all
##' languages), a character vector (a series of languages to use in
##' turn, listed from first to try to last to try) or a named list
##' of language-fallback mappings, e.g., \code{list("de-CH": c("fr",
##' "it"), "es": "fr")}.
##'
##' escape Logical, indicating if the translation output should
##' be, by default, escaped (see the i18next interpolation
##' documentation). The i18next implementation is to prevent xss
##' attacks, and so is disabled by default in traduire.
##' @param options traduire_options object
##'
##' @return A 'traduire_options' object
##' @export
##'
##' @examples
##' opts <- traduire::traduire_options()
##' opts <- traduire::traduire_options(language = "fr")
##' opts <- traduire::traduire_options(default_namespace = "tr", options = opts)
traduire_options <- function(..., options = NULL) {
args <- list(...)
if (length(args) > 0) {
assert_named(args, unique = TRUE, name = "options")
}
if (!is.null(options) && !is_traduire_options(options)) {
stop("Options must be of type 'traduire_options'")
}

if (is.null(options)) {
## TODO: better defaults for language, but there's lots to consider
## with fallbacks still
language <- args[["language"]] %||% "en"
assert_scalar_character(language)
validate_or_null(args[["default_namespace"]],
assert_scalar_character,
"default_namespace")
debug <- args[["debug"]] %||% FALSE
assert_scalar_logical(debug)
validate_or_null(args[["resource_pattern"]],
assert_scalar_character,
"resource_pattern")
namespaces <- args[["namespaces"]] %||% "translation"
assert_character(namespaces)
validate_or_null(args[["languages"]], assert_character, "languages")
fallback <- validate_fallback(args[["fallback"]] %||% "dev")
escape <- args[["escape"]] %||% FALSE
assert_scalar_logical(escape)
options <- list(
language = language,
default_namespace = args[["default_namespace"]],
debug = debug,
resource_pattern = args[["resource_pattern"]],
namespaces = namespaces,
languages = args[["languages"]],
fallback = fallback,
escape = escape)
options <- structure(options, class = "traduire_options")
} else {
if ("language" %in% names(args)) {
assert_scalar_character(args[["language"]], "language")
options[["language"]] <- args[["language"]]
}
if ("default_namespace" %in% names(args)) {
validate_or_null(args[["default_namespace"]],
assert_scalar_character,
"default_namespace")
options[["default_namespace"]] <- args[["default_namespace"]]
}
if ("debug" %in% names(args)) {
assert_scalar_logical(args[["debug"]], "debug")
options[["debug"]] <- args[["debug"]]
}
if ("resource_pattern" %in% names(args)) {
validate_or_null(args[["resource_pattern"]],
assert_scalar_character,
"resource_pattern")
options[["resource_pattern"]] <- args[["resource_pattern"]]
}
if ("namespaces" %in% names(args)) {
assert_character(args[["namespaces"]], "namesapces")
options[["namespaces"]] <- args[["namespaces"]]
}
if ("languages" %in% names(args)) {
validate_or_null(args[["languages"]], assert_character, "languages")
options[["languages"]] <- args[["languages"]]
}
if ("fallback" %in% names(args)) {
options[["fallback"]] <- validate_fallback(args[["fallback"]])
}
if ("escape" %in% names(args)) {
assert_scalar_logical(args[["escape"]], "escape")
options[["escape"]] <- args[["escape"]]
}
}
options
}

is_traduire_options <- function(obj) {
inherits(obj, "traduire_options")
}

validate_or_null <- function(x, fn, name = deparse(substitute(x))) {
if (!is.null(x)) {
fn(x, name)
}
}
2 changes: 1 addition & 1 deletion R/translator.R
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
##' @title Register a translator
##'
##' @param ... For \code{translator_register}, arguments passed to
##' \code{\link{i18n}} to build the translator object. All
##' \code{\link{traduire_options}} to build the translator object. All
##' arguments are accepted. For \code{translator_translate} and
##' \code{t_}, arguments passed to the \code{$t} method of the
##' translator object, being \code{string}, \code{data},
Expand Down
36 changes: 36 additions & 0 deletions R/util-assert.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
assert_named <- function(x, unique = FALSE, name = deparse(substitute(x))) {
if (is.null(names(x))) {
stop(sprintf("'%s' must be named", name), call. = FALSE)
}
if (unique && any(duplicated(names(x)))) {
stop(sprintf("'%s' must have unique names", name), call. = FALSE)
}
}

assert_scalar <- function(x, name = deparse(substitute(x))) {
if (length(x) != 1) {
stop(sprintf("'%s' must be a scalar", name), call. = FALSE)
}
}

assert_character <- function(x, name = deparse(substitute(x))) {
if (!is.character(x)) {
stop(sprintf("'%s' must be character", name), call. = FALSE)
}
}

assert_logical <- function(x, name = deparse(substitute(x))) {
if (!is.logical(x)) {
stop(sprintf("'%s' must be logical", name), call. = FALSE)
}
}

assert_scalar_logical <- function(x, name = deparse(substitute(x))) {
assert_scalar(x, name)
assert_logical(x, name)
}

assert_scalar_character <- function(x, name = deparse(substitute(x))) {
assert_scalar(x, name)
assert_character(x, name)
}
52 changes: 3 additions & 49 deletions man/i18n.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bc67d1b

Please sign in to comment.