diff --git a/DESCRIPTION b/DESCRIPTION index 9eaea69..af8f146 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: lang Title: Translates R Help Documentation using Large Language Models -Version: 0.1.1 +Version: 0.1.1.9000 Authors@R: c( person("Edgar", "Ruiz", , "edgar@posit.co", role = c("aut", "cre")), person("Posit Software, PBC", role = c("cph", "fnd"), diff --git a/NEWS.md b/NEWS.md index 33d0e50..4b77a2e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# lang 0.1.1.9000 + +## Bug fixes + +* When the input is 10 words or fewer, the context summary is now omitted from the translation prompt. Local LLMs can get confused by a context summary that is much longer than the field being translated, causing them to paraphrase the context instead of translating the input. + # lang 0.1.1 ## New features diff --git a/R/lang-help.R b/R/lang-help.R index f766ba3..1a1bc34 100644 --- a/R/lang-help.R +++ b/R/lang-help.R @@ -8,6 +8,14 @@ #' will be processed, so the help returned will be the original package's #' documentation. #' +#' To improve translation consistency, `lang` generates a short summary of the +#' full help page and passes it as context with each individual translation +#' request. This helps the LLM maintain consistent terminology across sections. +#' You can control the length of this summary with the `context_size` argument, +#' or set it to `0` to disable context-aware translation entirely. To avoid the +#' LLM getting confused when the context is much longer than the field being +#' translated, context is automatically omitted for fields of 10 words or fewer. +#' #' @param topic A character string specifying the help topic to translate. #' @param package The R package to look for the topic, if not provided the #' function will attempt to find the topic based on the loaded packages. diff --git a/R/rd-translate.R b/R/rd-translate.R index bcd9323..4cf1d8f 100644 --- a/R/rd-translate.R +++ b/R/rd-translate.R @@ -173,7 +173,9 @@ rd_translate <- function(rd_content, lang, context_size) { } rd_field_translate <- function(x, lang, rs, context_summary = NULL) { - context_block <- if (!is.null(context_summary)) { + word_count <- length(unlist(strsplit(paste(x, collapse = " "), " "))) + use_context <- !is.null(context_summary) && word_count > 10L + context_block <- if (use_context) { paste0( "For context, here is a short summary of the full help page in the target language:\n\n", context_summary, @@ -288,6 +290,12 @@ lang_rs_hash <- function() { } lang_rs_get <- function() { + if (is.null(.lang_env$session[["backend"]])) { + cli_abort( + "No LLM backend configured. Call {.fn lang_use} first.", + call = NULL + ) + } rs <- .lang_env$rs if (!is.null(rs) && rs$is_alive()) { if (rs$get_state() == "starting") { diff --git a/README.Rmd b/README.Rmd index 2bedd29..cd5f76b 100644 --- a/README.Rmd +++ b/README.Rmd @@ -76,16 +76,24 @@ Note that R enforces the printed names of each section, so titles such as "Description", "Usage", and "Arguments" will always remain untranslated. During translation, `lang` will display its progress by showing which section -of the documentation is currently translating. Because each section of a help -page is translated independently, the LLM can lose track of the broader topic -and produce inconsistent or out-of-context translations. To address this, -`lang` first summarizes the full help page in English, translates that summary -into the target language, and then uses it as context when translating each -individual section. You can control the length of this summary with the -`context_size` argument in `lang_use()` or `lang_help()` — set it to `0` to -disable it, or increase it to give the LLM more context. During the R session, -if you request the same R function's help more than one time then `lang` will -use its cached results, which will run immediately. +of the documentation is currently translating. During the R session, if you +request the same R function's help more than one time then `lang` will use its +cached results, which will run immediately. + +### Context summary + +Because each section of a help page is translated independently, the LLM can +lose track of the broader topic and produce inconsistent or out-of-context +translations. To address this, `lang` first summarizes the full help page in +English, translates that summary into the target language, and then uses it as +context when translating each individual section. You can control the length of +this summary with the `context_size` argument in `lang_use()` or `lang_help()` +— set it to `0` to disable it, or increase it to give the LLM more context. + +To avoid the LLM getting confused by a context summary that is longer than the +content being translated, context is automatically omitted for fields of 10 +words or fewer. + ### LLM connections @@ -228,6 +236,14 @@ use the full language name, such as 'spanish', or 'french', etc. You can use `Sys.setenv(LANGUAGE = "[my language]")`, or, for a more permanent solution, add the entry to your .Renviron file (`usethis::edit_r_environ()`). +### Translation errors + +If you experience unexpected translation errors and you are using a local LLM +without a `seed` set, try restarting your R session and running the translation +again. Non-deterministic LLM output can occasionally produce output that causes +errors. If the problem persists, please open an issue at +. + ### Interaction with `mall` `lang` uses the `mall` package to produce the translations. To avoid conflicts diff --git a/README.md b/README.md index 177f5c2..24a632a 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,34 @@ ---- -output: github_document ---- - - lang's hex logo -# lang +# lang + + [![R-CMD-check](https://github.com/mlverse/lang/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlverse/lang/actions/workflows/R-CMD-check.yaml) -[![Codecov test coverage](https://codecov.io/gh/mlverse/lang/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/lang?branch=main) +[![Codecov test +coverage](https://codecov.io/gh/mlverse/lang/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mlverse/lang?branch=main) CRAN status -Use an **LLM to translate a function's help documentation on the fly**. `lang` -overrides the `?` and `help()` functions in your R session. If you are using -RStudio or Positron, the translated help page will appear in the 'Help' -pane. +Use an **LLM to translate a function’s help documentation on the fly**. +`lang` overrides the `?` and `help()` functions in your R session. If +you are using RStudio or Positron, the translated help page will appear +in the ‘Help’ pane. ## Installing To install the CRAN version of `lang` use: -```r +``` r install.packages("lang") ``` To install the GitHub version of `lang`, use: -```r +``` r install.packages("pak") pak::pak("mlverse/lang") ``` @@ -38,15 +36,16 @@ pak::pak("mlverse/lang") ## Using `lang` In order to work, `lang` needs two things: - - 1. An LLM connection - - 1. A target language (e.g.: Spanish, French, Korean) -These two can be defined using `lang_use()`. For example, the following code -shows how to use OpenAI's GPT-4o model to translate `lm()`'s help into Spanish: +1. An LLM connection + +2. A target language (e.g.: Spanish, French, Korean) -```r +These two can be defined using `lang_use()`. For example, the following +code shows how to use OpenAI’s GPT-4o model to translate `lm()`’s help +into Spanish: + +``` r library(lang) chat <- ellmer::chat_openai(model = "gpt-4o") @@ -56,73 +55,83 @@ lang_use(backend = chat, .lang = "spanish") ?lm #> ■■ 4% | Title ``` + Screenshot of the lm function's help page in Spanish -After setup, simply use `?` to trigger and display the translated documentation. -Note that R enforces the printed names of each section, so titles such as -"Description", "Usage", and "Arguments" will always remain untranslated. +After setup, simply use `?` to trigger and display the translated +documentation. Note that R enforces the printed names of each section, +so titles such as “Description”, “Usage”, and “Arguments” will always +remain untranslated. -During translation, `lang` will display its progress by showing which section -of the documentation is currently translating. Because each section of a help -page is translated independently, the LLM can lose track of the broader topic -and produce inconsistent or out-of-context translations. To address this, -`lang` first summarizes the full help page in English, translates that summary -into the target language, and then uses it as context when translating each -individual section. You can control the length of this summary with the -`context_size` argument in `lang_use()` or `lang_help()` — set it to `0` to -disable it, or increase it to give the LLM more context. During the R session, -if you request the same R function's help more than one time then `lang` will -use its cached results, which will run immediately. +During translation, `lang` will display its progress by showing which +section of the documentation is currently translating. During the R +session, if you request the same R function’s help more than one time +then `lang` will use its cached results, which will run immediately. +### Context summary + +Because each section of a help page is translated independently, the LLM +can lose track of the broader topic and produce inconsistent or +out-of-context translations. To address this, `lang` first summarizes +the full help page in English, translates that summary into the target +language, and then uses it as context when translating each individual +section. You can control the length of this summary with the +`context_size` argument in `lang_use()` or `lang_help()` — set it to `0` +to disable it, or increase it to give the LLM more context. + +To avoid the LLM getting confused by a context summary that is longer +than the content being translated, context is automatically omitted for +fields of 10 words or fewer. ### LLM connections There are two ways to define the LLM in `lang_use()`: -1. Use an [`ellmer`](https://ellmer.tidyverse.org/) chat object: +1. Use an [`ellmer`](https://ellmer.tidyverse.org/) chat object: - ```r + ``` r lang_use(backend = ellmer::chat_openai(model = "gpt-4o")) ``` -1. Use local LLMs available through [Ollama](https://ollama.com/). Pass `"ollama"` -as the `backend` argument, and specify which installed model to use: +2. Use local LLMs available through [Ollama](https://ollama.com/). Pass + `"ollama"` as the `backend` argument, and specify which installed + model to use: - ```r + ``` r lang_use(backend = "ollama", model = "llama3.2", seed = 100) ``` - Under the hood, `lang` uses the [`ollamar`](https://hauselin.github.io/ollama-r/) - package to integrate with Ollama. Any additional arguments, such as `seed` - as shown above, will be passed as-is to `ollamar`'s `chat()` function. + Under the hood, `lang` uses the + [`ollamar`](https://hauselin.github.io/ollama-r/) package to + integrate with Ollama. Any additional arguments, such as `seed` as + shown above, will be passed as-is to `ollamar`’s `chat()` function. ### Target language +In order of priority, these are the ways in which `lang` determines the +language it will translate to: -In order of priority, these are the ways in which `lang` determines the language -it will translate to: - -1. Value in `.lang` when calling `lang_use()` -1. `LANGUAGE` environment variable -1. `LANG` environment variable +1. Value in `.lang` when calling `lang_use()` +2. `LANGUAGE` environment variable +3. `LANG` environment variable It is likely that your `LANG` variable already defaults to your locale. -For example, mine is set to: `en_US.UTF-8` (that means English, United States). -For someone in France, the locale would be something such as `fr_FR.UTF-8`. -Llama3.2 recognizes these UTF locales, and using `lang`, calling `?` will -result in translating the function's help documentation into French. - -If both environment variables are set, and are different from each other, -`lang` will display a one-time message indicating which value it will use. -If the target language is English, `lang` will re-route help calls back to base -R. - -To check the current target language at any point during the R session, +For example, mine is set to: `en_US.UTF-8` (that means English, United +States). For someone in France, the locale would be something such as +`fr_FR.UTF-8`. Llama3.2 recognizes these UTF locales, and using `lang`, +calling `?` will result in translating the function’s help documentation +into French. + +If both environment variables are set, and are different from each +other, `lang` will display a one-time message indicating which value it +will use. If the target language is English, `lang` will re-route help +calls back to base R. + +To check the current target language at any point during the R session, simply run: `lang_use()`, with no arguments, and it will print out the current settings, which include language: - ``` r lang_use() #> Model: gpt-4o via OpenAI @@ -133,14 +142,14 @@ lang_use() ### Caching -By default, `lang` will cache the translations it performs in a temporary folder. -If R is restarted, a new folder will be used. +By default, `lang` will cache the translations it performs in a +temporary folder. If R is restarted, a new folder will be used. -If you notice that you are translating the same function's help over and over and -across different R sessions, then fixing the cache location would be helpful. Use -`.cache` to define the folder: +If you notice that you are translating the same function’s help over and +over and across different R sessions, then fixing the cache location +would be helpful. Use `.cache` to define the folder: -```r +``` r lang::lang_use( backend = "ollama", model = "llama3.2", @@ -149,21 +158,21 @@ lang::lang_use( ) ``` - ### Auto-initialize at startup -If `lang` becomes a regular part of your workflow, and running `lang_use()` at -the beginning of every R session becomes cumbersome, then consider letting R -connect at start up. +If `lang` becomes a regular part of your workflow, and running +`lang_use()` at the beginning of every R session becomes cumbersome, +then consider letting R connect at start up. -If present, the *.Rprofile* file runs at the beginning of any R session. If you -wish to automatically set the model and language to use, add a call to `lang_use()` -to this file. You can call `usethis::edit_r_profile()` to open your .Rprofile -file so you can add the option. +If present, the *.Rprofile* file runs at the beginning of any R session. +If you wish to automatically set the model and language to use, add a +call to `lang_use()` to this file. You can call +`usethis::edit_r_profile()` to open your .Rprofile file so you can add +the option. Here is an example using Ollama: -```r +``` r lang::lang_use( backend = "ollama", model = "llama3.2", @@ -175,7 +184,7 @@ lang::lang_use( And here is an example using an `ellmer` chat object: -```r +``` r lang::lang_use( backend = ellmer::chat_openai(model = "gpt-4o"), .cache = "~/help-translations/", @@ -184,51 +193,59 @@ lang::lang_use( ) ``` -In both examples, `.silent` is set to `TRUE` so that there is no message every -time the R session is restarted. The `.cache` argument points to a fixed folder -so that translations persist across sessions. You can also set `.context_size` -here to control how much context the LLM receives when translating each section. +In both examples, `.silent` is set to `TRUE` so that there is no message +every time the R session is restarted. The `.cache` argument points to a +fixed folder so that translations persist across sessions. You can also +set `.context_size` here to control how much context the LLM receives +when translating each section. ## Considerations ### Translations are not perfect -As you can imagine, the quality of translation will mostly depend on the LLM -being used. This solution is meant to be as helpful as possible, but -we acknowledge that at this stage of LLMs, only a human curated translation -will be the best solution. Having said that, I believe that even an imperfect -translation could go a long way with someone who is struggling to understand -how to use a specific function in a package and may also struggle with the -English language. +As you can imagine, the quality of translation will mostly depend on the +LLM being used. This solution is meant to be as helpful as possible, but +we acknowledge that at this stage of LLMs, only a human curated +translation will be the best solution. Having said that, I believe that +even an imperfect translation could go a long way with someone who is +struggling to understand how to use a specific function in a package and +may also struggle with the English language. ### Debugging -If the original English help page displays, check your environment variables: - +If the original English help page displays, check your environment +variables: ``` r Sys.getenv("LANG") -#> [1] "" +#> [1] "en_US.UTF-8" Sys.getenv("LANGUAGE") #> [1] "" ``` -In my case, `lang` recognizes that the environment is set to English, because -of the `en` code in the variable. If your `LANG` variable is set to `en_...` -then no translation will occur. +In my case, `lang` recognizes that the environment is set to English, +because of the `en` code in the variable. If your `LANG` variable is set +to `en_...` then no translation will occur. -If this is your case, set the `LANGUAGE` variable to your preference. You can -use the full language name, such as 'spanish', or 'french', etc. You can use -`Sys.setenv(LANGUAGE = "[my language]")`, or, for a more permanent solution, -add the entry to your .Renviron file (`usethis::edit_r_environ()`). +If this is your case, set the `LANGUAGE` variable to your preference. +You can use the full language name, such as ‘spanish’, or ‘french’, etc. +You can use `Sys.setenv(LANGUAGE = "[my language]")`, or, for a more +permanent solution, add the entry to your .Renviron file +(`usethis::edit_r_environ()`). -### Interaction with `mall` - -`lang` uses the `mall` package to produce the translations. To avoid conflicts -in the setup and use of both packages during the R session, `lang` runs `mall` -in a separate R process which is only alive while translating the documentation. -This means that you can have a specific LLM setup for `lang`, and a different -one for `mall` during your R session. +### Translation errors +If you experience unexpected translation errors and you are using a +local LLM without a `seed` set, try restarting your R session and +running the translation again. Non-deterministic LLM output can +occasionally produce output that causes errors. If the problem persists, +please open an issue at . +### Interaction with `mall` +`lang` uses the `mall` package to produce the translations. To avoid +conflicts in the setup and use of both packages during the R session, +`lang` runs `mall` in a separate R process which is only alive while +translating the documentation. This means that you can have a specific +LLM setup for `lang`, and a different one for `mall` during your R +session. diff --git a/man/lang_help.Rd b/man/lang_help.Rd index 0e0c363..939e013 100644 --- a/man/lang_help.Rd +++ b/man/lang_help.Rd @@ -40,6 +40,14 @@ if something has been passed to the \code{.lang} argument in \code{lang_use()}, determine the target language. If the target language is English, no translation will be processed, so the help returned will be the original package's documentation. + +To improve translation consistency, \code{lang} generates a short summary of the +full help page and passes it as context with each individual translation +request. This helps the LLM maintain consistent terminology across sections. +You can control the length of this summary with the \code{context_size} argument, +or set it to \code{0} to disable context-aware translation entirely. To avoid the +LLM getting confused when the context is much longer than the field being +translated, context is automatically omitted for fields of 10 words or fewer. } \examples{ \dontrun{ diff --git a/tests/testthat/_snaps/aaa-nosetup.md b/tests/testthat/_snaps/aaa-nosetup.md new file mode 100644 index 0000000..44f0f37 --- /dev/null +++ b/tests/testthat/_snaps/aaa-nosetup.md @@ -0,0 +1,8 @@ +# lang_help() errors clearly when no backend is configured + + Code + lang_help("lm", "stats", lang = "spanish") + Condition + Error: + ! No LLM backend configured. Call `lang_use()` first. + diff --git a/tests/testthat/_snaps/zzz-local.md b/tests/testthat/_snaps/zzz-local.md new file mode 100644 index 0000000..36eb6c3 --- /dev/null +++ b/tests/testthat/_snaps/zzz-local.md @@ -0,0 +1,229 @@ +# rd_translate() produces correct output with Ollama + + Code + rd_test_translate(test_path("rd/lang_help.Rd")) + Message + v lang - Translation complete + Output + _D_o_c_u_m_e_n_t_a_c_i_ó_n _d_e _a_y_u_d_a _p_a_r_a _t_r_a_d_u_c_i_r _a _o_t_r_o _i_d_i_o_m_a. + + _D_e_s_c_r_i_p_t_i_o_n: + + Traduce un tema dado a otro idioma. Utiliza el argumento 'lang' + para determinar qué idioma traducir. Si no se pasa este argumento, + esta función buscará un idioma objetivo en las variables de + entorno LANG y LANGUAGE o, si se ha pasado algún argumento al + '.lang()' en la función 'lang_use()', para determinar el idioma + objetivo. Si el idioma objetivo es inglés, no se procesará la + traducción, por lo que se devolverá la documentación del paquete + original. + + _U_s_a_g_e: + + lang_help( + topic, + package = NULL, + lang = NULL, + context_size = NULL, + type = getOption("help_type") + ) + + _A_r_g_u_m_e_n_t_s: + + topic: Texto de ayuda para traducir. + + package: El paquete de R a buscar se proporciona como argumento, si no + está disponible la función intentará encontrar el tema en las + bibliotecas cargadas. + + lang: Caracter vector de idioma para traducir el tema a + + context_size: Número máximo de palabras para la suma de contexto + incluido con cada solicitud de traducción. Establecido en '0' + para desactivar la traducción contextual. Cuando es 'NULL', + el valor establecido mediante 'lang_use()' se utiliza (por + defecto, '100'). + + type: Produce "html" o "text" salida para la ayuda. Se configura + por defecto con la opción 'getOption("help_type")'. + + _V_a_l_u_e: + + Aquí está una descripción general del producto: + + * Traduce la documentación de ayuda a otro idioma * Utiliza el + argumento 'lang', variables de entorno y la función '.lang()' para + determinar el idioma objetivo * Asume un paquete (opcional), un + tema, un idioma, un tamaño de contexto y un tipo (html/text) como + entrada * Regresa el valor 'intro' en el tipo de salida deseado. + + La versión original o traducida de la documentación del producto + en el formato especificado. + + _E_x_a_m_p_l_e_s: + + # Requiere una sesión interactiva con Ollama ejecutada localmente. + library(lang) + + lang_use("ollama", "llama3.2", seed = 100) + + lang_help("lang_help", lang = "spanish", type = "text") + + +--- + + Code + rd_test_translate(test_path("rd/aes.rds")) + Message + v lang - Translation complete + Output + _C_o_n_s_t_r_u_i_r _m_a_p_e_o_s _e_s_t_é_t_i_c_o_s + + _D_e_s_c_r_i_p_t_i_o_n: + + Las mapeos estéticos describen cómo las variables en los datos se + mapan a las propiedades visuales (estética) de geoms. Los mapeos + estéticos pueden ser configurados con 'ggplot()' y en capas + individuales. Se omite comúnmente el eje x y y, mientras que todas + las otras propiedades deben ser nombradas. + + La interpolación quasisinónica permite un uso fácil con variables + de la hoja de datos nombrándolas directamente. 'vars()' es otra + función de citado diseñada para especificaciones de capas. La + evaluación atrasada permite trabajar con variables computadas. + Documentación adicional sobre otras estéticas se puede encontrar + en 'aes_colour_fill_alpha', 'aes_group_order', + 'aes_linetype_size_shape', 'aes_position'. + + _U_s_a_g_e: + + aes(x, y, ...) + + _A_r_g_u_m_e_n_t_s: + + x, y, ...: La lista de pares de nombres que describe cómo las variables + en los datos se mapan a las propiedades visuales (estética) + de geoms. La lista de pares de nombres puede ser configurada + con 'ggplot()' y en capas individuales. Se omite comúnmente + el eje x y y, mientras que todas las otras propiedades deben + ser nombradas. + + La interpolación quasisinónica permite un uso fácil con + variables de la hoja de datos nombrándolas directamente. + 'vars()' es otra función para especificaciones de capas. La + evaluación atrasada permite trabajar con variables + computadas. + + Se puede encontrar más información sobre otras estéticas en + 'aes_colour_fill_alpha', 'aes_group_order', + 'aes_linetype_size_shape', 'aes_position'. + + _D_e_t_a_i_l_s: + + Esta función también estándariza los nombres de estéticas al + convertir 'color' a 'colour' (también en subcadenas, como + 'point_color' a 'point_colour') y traduciendo los nombres estilo R + antiguos a los nombres de ggplot (por ejemplo, 'pch' a 'shape' y + 'cex' a 'size'). + + _V_a_l_u_e: + + Un objeto S7 que representa una lista con clase 'mapping'. + Componentes de la lista son Either constantes o expresiones. + + _C_i_t_a_c_i_ó_n _q_u_a_s_e: + + La función 'aes()' es una función de citación. Esto significa que + sus entradas se citan para ser evaluadas en el contexto de los + datos. Esto hace que sea fácil trabajar con variables del data + frame porque puedes nombrarlas directamente. La contradicción es + que debes usar citación quasiana para programar con 'aes()'. + Consulta una tutorial de evaluación quirúrgica en la sección de + programación de dplyr para aprender más sobre estos técnicas. + + _N_o_t_e: + + Usando 'I()' para crear objetos de clase 'AsIs' hace que las + escalas ignoren la variable y asuma que el valor wrapado es una + entrada directa al paquete de gráficos. Se debe tener en cuenta + que las variables a veces se combinan, como en algunos ajustes + estadísticos o ajustes de posición, lo que puede generar + resultados inesperados con las variables 'AsIs'. + + _S_e_e _A_l_s_o: + + Las mapeos estéticos describen cómo las variables en los datos se + mapan a las propiedades visuales (estética) de geoms. Los mapeos + estéticos pueden ser configurados con 'ggplot()' y en capas + individuales. Se omite comúnmente el eje x y y, mientras que todas + las otras propiedades deben ser nombradas. + + La interpolación quasisinónica permite un uso fácil con variables + de la hoja de datos nombrándolas directamente. 'vars()' es otra + función diseñada para especificaciones de capas. La evaluación + atrasada permite trabajar con variables computadas. Documentación + adicional sobre otras estéticas se puede encontrar en + 'aes_colour_fill_alpha', 'aes_group_order', + 'aes_linetype_size_shape', 'aes_position'. + + Para ver un resumen de otras estéticas que pueden modificarse, + utilice el comando 'vignette("ggplot2-specs")'. + + La evaluación atrasada es útil para trabajar con variables + calculadas. + + Otra documentación sobre estéticas adicionales se puede encontrar + en 'aes_colour_fill_alpha', 'aes_group_order', + 'aes_linetype_size_shape', 'aes_position' + + _E_x_a_m_p_l_e_s: + + aes(x = mpg, y = wt) + aes(mpg, wt) + + # Puedes también mapear estéticas a funciones de variables. + aes(x = mpg ^ 2, y = wt / cyl) + + # O para constantes + aes(x = 1, colour = "smooth") + + # Los nombres de las estéticas se normalizan automáticamente. + aes(col = x) + aes(fg = x) + aes(color = x) + aes(colour = x) + + # Las estéticas (`aes`) se pasan a la función `ggplot()` o específica capa, y los esteticismos suministrados se utilizan para especificar las propiedades de los géomos. + # Se utilizan como defaults para cada capa de ggplot(). + ggplot(mpg, aes(displ, hwy)) + geom_point() + ggplot(mpg) + geom_point(aes(displ, hwy)) + + # La evaluación atrasada permite trabajar con variables computadas. + # Las mapeos estéticos describen cómo las variables en los datos se mapan a las propiedades visuales (estética) de geoms. Los mapeos estéticos pueden ser configurados con `ggplot()` y en capas individuales. Se omite comúnmente el eje x y y, mientras que todas las otras propiedades deben ser nombradas. + + La interpolación quasisinónica permite un uso fácil con variables de la hoja de datos nombrándolas directamente. + `vars()` es otra función de citado diseñada para especificaciones de capas. + La evaluación atrasada permite trabajar con variables computadas. + Documentación adicional sobre otras estéticas se puede encontrar en `aes_colour_fill_alpha`, `aes_group_order`, `aes_linetype_size_shape`, `aes_position`. + + La interpolación quasisinónica permite un uso fácil con variables de la hoja de datos nombrándolas directamente. + # La evaluación permite crear envolturas alrededor de los pipelines de ggplot2. + # El caso más simple ocurre cuando tu envoltura toma puntos. + scatter_by <- function(data, ...) { + ggplot(data) + geom_point(aes(...)) + } + scatter_by(mtcars, disp, drat) + + # Si tu envoltura tiene una interfaz más específica con argumentos nombrados. + # "necesitas el operador de abrazo:" + scatter_by <- function(data, x, y) { + ggplot(data) + geom_point(aes({{ x }}, {{ y }})) + } + scatter_by(mtcars, disp, drat) + + # Señores del mapeo, es posible utilizar sus propias funciones dentro de la capa estética. + # Las expresiones encuadradas y todas resolverán como deberían. + cut3 <- function(x) cut_number(x, 3) + scatter_by(mtcars, cut3(disp), drat) + + diff --git a/tests/testthat/helper-utils.R b/tests/testthat/helper-utils.R index 8afa9d5..5e46f8a 100644 --- a/tests/testthat/helper-utils.R +++ b/tests/testthat/helper-utils.R @@ -1,3 +1,13 @@ +rd_test_translate <- function(rd_path, lang = "spanish") { + rd_content <- if (grepl("\\.rds$", rd_path)) { + readRDS(rd_path) + } else { + tools::parse_Rd(rd_path) + } + tmp <- rd_translate(rd_content, lang, context_size = 100L) + tools::Rd2txt(tmp) +} + simulate_ellmer <- function() { setClass( Class = "simulate_provider", diff --git a/tests/testthat/rd/aes.Rd b/tests/testthat/rd/aes.Rd new file mode 100644 index 0000000..b7ad121 --- /dev/null +++ b/tests/testthat/rd/aes.Rd @@ -0,0 +1,417 @@ +\title +{ +Construct aesthetic mappings +} +\name +{ +aes +} +\alias +{ +aes +} +\concept +{ +aesthetics documentation +} +\description +{ + + +Aesthetic mappings describe how variables in the data are mapped to visual + +properties (aesthetics) of geoms. Aesthetic mappings can be set in + +\code +{ +\link +[ +=ggplot +] +{ +ggplot() +} +} + and in individual layers. + +} +\usage +{ + + +aes(x, y, ...) + +} +\arguments +{ + + +\item + +{ +x, y, ... +} + +{ +< +\code +{ +\link +[ +rlang:topic-data-mask +] +{ +data-masking +} +} +> List of name-value + +pairs in the form +\code +{ +aesthetic = variable +} + describing which variables in the + +layer data should be mapped to which aesthetics used by the paired + +geom/stat. The expression +\code +{ +variable +} + is evaluated within the layer data, so + +there is no need to refer to the original dataset (i.e., use + +\code +{ +ggplot(df, aes(variable)) +} + instead of +\code +{ +ggplot(df, aes(df$variable)) +} +). + +The names for x and y aesthetics are typically omitted because they are so + +common; all other aesthetics must be named. +} + + +} +\details +{ + + +This function also standardises aesthetic names by converting +\code +{ +color +} + to +\code +{ +colour +} + + +(also in substrings, e.g., +\code +{ +point_color +} + to +\code +{ +point_colour +} +) and translating old style + +R names to ggplot names (e.g., +\code +{ +pch +} + to +\code +{ +shape +} + and +\code +{ +cex +} + to +\code +{ +size +} +). + +} +\value +{ + + +An S7 object representing a list with class +\code +{ +mapping +} +. Components of + +the list are either quosures or constants. + +} +\section + +{ +Quasiquotation +} + +{ + + + + + + +\code +{ +aes() +} + is a +\link +[ +rlang:topic-defuse +] +{ +quoting function +} +. This means that + +its inputs are quoted to be evaluated in the context of the + +data. This makes it easy to work with variables from the data frame + +because you can name those directly. The flip side is that you have + +to use +\link +[ +rlang:topic-inject +] +{ +quasiquotation +} + to program with + +\code +{ +aes() +} +. See a tidy evaluation tutorial such as the +\href + +{ +https://dplyr.tidyverse.org/articles/programming.html +} + +{ +dplyr programming vignette +} + + +to learn more about these techniques. + +} +\note +{ + + +Using +\code +{ +I() +} + to create objects of class 'AsIs' causes scales to ignore the + +variable and assumes the wrapped variable is direct input for the grid + +package. Please be aware that variables are sometimes combined, like in + +some stats or position adjustments, that may yield unexpected results with + +'AsIs' variables. + +} +\seealso +{ + + +\code +{ +\link +[ +=vars +] +{ +vars() +} +} + for another quoting function designed for + +faceting specifications. + + + +Run +\code +{ +vignette("ggplot2-specs") +} + to see an overview of other aesthetics + +that can be modified. + + + +\link +[ +=aes_eval +] +{ +Delayed evaluation +} + for working with computed variables. + + + +Other aesthetics documentation: + +\code +{ +\link +{ +aes_colour_fill_alpha +} +} +, + +\code +{ +\link +{ +aes_group_order +} +} +, + +\code +{ +\link +{ +aes_linetype_size_shape +} +} +, + +\code +{ +\link +{ +aes_position +} +} + + +} +\examples +{ + + +aes(x = mpg, y = wt) + +aes(mpg, wt) + + + +# You can also map aesthetics to functions of variables + +aes(x = mpg ^ 2, y = wt / cyl) + + + +# Or to constants + +aes(x = 1, colour = "smooth") + + + +# Aesthetic names are automatically standardised + +aes(col = x) + +aes(fg = x) + +aes(color = x) + +aes(colour = x) + + + +# aes() is passed to either ggplot() or specific layer. Aesthetics supplied + +# to ggplot() are used as defaults for every layer. + +ggplot(mpg, aes(displ, hwy)) + geom_point() + +ggplot(mpg) + geom_point(aes(displ, hwy)) + + + +# Tidy evaluation ---------------------------------------------------- + +# aes() automatically quotes all its arguments, so you need to use tidy + +# evaluation to create wrappers around ggplot2 pipelines. The + +# simplest case occurs when your wrapper takes dots: + +scatter_by <- function(data, ...) { + + ggplot(data) + geom_point(aes(...)) + +} + +scatter_by(mtcars, disp, drat) + + + +# If your wrapper has a more specific interface with named arguments, + +# you need the "embrace operator": + +scatter_by <- function(data, x, y) { + + ggplot(data) + geom_point(aes({{ x }}, {{ y }})) + +} + +scatter_by(mtcars, disp, drat) + + + +# Note that users of your wrapper can use their own functions in the + +# quoted expressions and all will resolve as it should! + +cut3 <- function(x) cut_number(x, 3) + +scatter_by(mtcars, cut3(disp), drat) + +} diff --git a/tests/testthat/rd/aes.rds b/tests/testthat/rd/aes.rds new file mode 100644 index 0000000..b956a09 Binary files /dev/null and b/tests/testthat/rd/aes.rds differ diff --git a/tests/testthat/rd/lang_help.Rd b/tests/testthat/rd/lang_help.Rd new file mode 100644 index 0000000..0e0c363 --- /dev/null +++ b/tests/testthat/rd/lang_help.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lang-help.R +\name{lang_help} +\alias{lang_help} +\title{Translates help documentation to another language} +\usage{ +lang_help( + topic, + package = NULL, + lang = NULL, + context_size = NULL, + type = getOption("help_type") +) +} +\arguments{ +\item{topic}{A character string specifying the help topic to translate.} + +\item{package}{The R package to look for the topic, if not provided the +function will attempt to find the topic based on the loaded packages.} + +\item{lang}{A character vector language to translate the topic to} + +\item{context_size}{Maximum number of words for the context summary included +with each translation request. Set to \code{0} to disable context-aware +translation. When \code{NULL}, the value set via \code{lang_use()} is used (default +\code{100}).} + +\item{type}{Produce "html" or "text" output for the help. It defaults to +\code{getOption("help_type")}} +} +\value{ +Original or translated version of the help documentation in the +output type specified +} +\description{ +Translates a given topic into a target language. It uses the \code{lang} argument +to determine which language to translate to. If not passed, this function will +look for a target language in the LANG and LANGUAGE environment variables, or +if something has been passed to the \code{.lang} argument in \code{lang_use()}, to +determine the target language. If the target language is English, no translation +will be processed, so the help returned will be the original package's +documentation. +} +\examples{ +\dontrun{ +# Requires an interactive session with Ollama running locally +library(lang) + +lang_use("ollama", "llama3.2", seed = 100) + +lang_help("lang_help", lang = "spanish", type = "text") +} + +} diff --git a/tests/testthat/test-aaa-nosetup.R b/tests/testthat/test-aaa-nosetup.R new file mode 100644 index 0000000..3301349 --- /dev/null +++ b/tests/testthat/test-aaa-nosetup.R @@ -0,0 +1,8 @@ +# These tests run first (aaa) to verify behavior before any lang_use() setup. + +test_that("lang_help() errors clearly when no backend is configured", { + expect_snapshot( + error = TRUE, + lang_help("lm", "stats", lang = "spanish") + ) +}) diff --git a/tests/testthat/test-zzz-local.R b/tests/testthat/test-zzz-local.R new file mode 100644 index 0000000..a01599e --- /dev/null +++ b/tests/testthat/test-zzz-local.R @@ -0,0 +1,28 @@ +# ---- Ollama integration (local only, skipped on CRAN) ------------------------ +# These tests run last to avoid interfering with simulate_llm state in other tests. + +test_that("rd_translate() produces correct output with Ollama", { + skip_on_cran() + skip_if( + !isTRUE(tryCatch( + { + con <- url("http://localhost:11434") + suppressWarnings(open(con)) + close(con) + TRUE + }, + error = \(e) FALSE, + warning = \(e) FALSE + )), + "Ollama is not running locally" + ) + lang_use_impl( + "ollama", + "llama3.2", + seed = 374, + temp = NULL, + .is_internal = TRUE + ) + expect_snapshot(rd_test_translate(test_path("rd/lang_help.Rd"))) + expect_snapshot(rd_test_translate(test_path("rd/aes.rds"))) +})