diff --git a/cmd/root.go b/cmd/root.go index 2ef2bd4a..d21fb691 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -106,6 +106,7 @@ func init() { RootCmd.PersistentFlags().Duration("plugin-discovery-timeout", 2*time.Second, "timeout for plugin discovery (0s disables)") RootCmd.PersistentFlags().Duration("plugin-update-check-interval", internalPlugin.DefaultUpdateCheckInterval, "cooldown between plugin update checks (0s disables)") RootCmd.PersistentFlags().Bool("skip-plugin-update-check", false, "skip plugin update checks before running plugins") + RootCmd.PersistentFlags().String("locale", "", "locale for plugins (e.g., en, ja)") // Make some of these flags available via Viper _ = viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config")) @@ -116,6 +117,7 @@ func init() { _ = viper.BindPFlag("plugin-discovery-timeout", RootCmd.PersistentFlags().Lookup("plugin-discovery-timeout")) _ = viper.BindPFlag("plugin-update-check-interval", RootCmd.PersistentFlags().Lookup("plugin-update-check-interval")) _ = viper.BindPFlag("skip-plugin-update-check", RootCmd.PersistentFlags().Lookup("skip-plugin-update-check")) + _ = viper.BindPFlag("locale", RootCmd.PersistentFlags().Lookup("locale")) // Add command groups (plugin group added conditionally by registerPluginCommands) RootCmd.AddGroup( @@ -204,5 +206,3 @@ func initializeConfig(cmd *cobra.Command) error { return nil } - - diff --git a/docs/commands/README.md b/docs/commands/README.md index 104bb992..63a3e0e1 100644 --- a/docs/commands/README.md +++ b/docs/commands/README.md @@ -16,6 +16,7 @@ These flags are available for all commands: --skip-auth Skip authentication checks (for advanced users) --force-interactive Force the setup wizard to run even if already completed --all-commands Display all available commands and their flags in tree format + --locale string Locale for plugins (e.g., en, ja) --plugin-discovery-timeout duration Timeout for plugin discovery (e.g. 2s, 500ms; default: 2s; 0s disables) -h, --help Show help information ``` @@ -260,6 +261,7 @@ Global environment variables that affect all commands: DATAROBOT_ENDPOINT # DataRobot URL DATAROBOT_API_TOKEN # API token (not recommended) DATAROBOT_CLI_CONFIG # Path to config file +DATAROBOT_CLI_LOCALE # Locale for plugins (e.g., en, ja) DATAROBOT_CLI_PLUGIN_DISCOVERY_TIMEOUT # Timeout for plugin discovery (e.g. 2s; 0s disables) VISUAL # External editor for file editing EDITOR # External editor for file editing (fallback) diff --git a/docs/development/plugins.md b/docs/development/plugins.md index e9562cae..73233800 100644 --- a/docs/development/plugins.md +++ b/docs/development/plugins.md @@ -117,6 +117,10 @@ When `authentication` is enabled: - Authentication can be bypassed with the global `--skip-auth` flag (for advanced users). - Your plugin will receive a clean environment with authentication already validated +### Locale + +When the user configures a locale via `drconfig.yaml`, the `--locale` flag, or the `DATAROBOT_CLI_LOCALE` environment variable, the CLI passes it to plugins as the `DR_LOCALE` environment variable. Plugins can use this as the highest-priority locale source, falling back to their own detection logic when `DR_LOCALE` is not set. + ## Developing a plugin Minimum requirements: diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index 704f9301..5914ca22 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -30,12 +30,16 @@ The main configuration file (`drconfig.yaml`) stores your DataRobot connection s # DataRobot Connection endpoint: DATA_ROBOT_ENDPOINT_URL # e.g. https://app.datarobot.com token: API_KEY_HERE + +# Optional: locale for plugins (e.g., en, ja) +# locale: ja ``` **Configuration fields:** - `endpoint`: Your DataRobot instance URL (e.g., `https://app.datarobot.com`) - `token`: Your API authentication token (automatically stored after `dr auth login`) +- `locale`: Locale for plugins (e.g., `en`, `ja`). When set, passed to plugins as `DR_LOCALE` environment variable. > [!NOTE] > You typically don't need to edit this file manually. The CLI manages it automatically when you use `dr auth set-url` and `dr auth login`. diff --git a/go.mod b/go.mod index 46da986e..27fcaa29 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/ulikunitz/xz v0.5.15 + golang.org/x/term v0.36.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -75,7 +76,6 @@ require ( golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.36.0 // indirect golang.org/x/text v0.30.0 // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect ) diff --git a/internal/config/constants.go b/internal/config/constants.go index 309e51c9..4db794bb 100644 --- a/internal/config/constants.go +++ b/internal/config/constants.go @@ -17,4 +17,5 @@ package config const ( DataRobotURL = "endpoint" DataRobotAPIKey = "token" + DataRobotLocale = "locale" ) diff --git a/internal/drapi/llms.go b/internal/drapi/llms.go index 86edfa80..4902abcb 100644 --- a/internal/drapi/llms.go +++ b/internal/drapi/llms.go @@ -24,7 +24,7 @@ type LLM struct { Provider string `json:"provider"` IsActive bool `json:"isActive"` Model string `json:"model"` - + //Version string `json:"version"` //Description string `json:"description"` //Creator string `json:"creator"` diff --git a/internal/plugin/exec.go b/internal/plugin/exec.go index bf6f7844..c822a5e2 100644 --- a/internal/plugin/exec.go +++ b/internal/plugin/exec.go @@ -126,6 +126,11 @@ func buildPluginEnv(pluginPath string, requireAuth bool) []string { env = append(env, "DATAROBOT_CONFIG="+configPath) } + // Propagate locale setting to plugins when explicitly configured + if locale := viper.GetString(config.DataRobotLocale); locale != "" { + env = append(env, "DR_LOCALE="+locale) + } + if !requireAuth { return env } diff --git a/internal/plugin/exec_test.go b/internal/plugin/exec_test.go index 4aaf54f9..59fc6a21 100644 --- a/internal/plugin/exec_test.go +++ b/internal/plugin/exec_test.go @@ -187,3 +187,74 @@ func TestExecutePluginCustomUserAgent(t *testing.T) { assert.Equal(t, "DataRobot CLI plugin: test-plugin (version 1.2.3)", capturedUserAgent) } + +func TestBuildPluginEnvLocale(t *testing.T) { + tests := []struct { + name string + locale string + expectEnv bool + expectedVal string + }{ + {"locale set to ja", "ja", true, "ja"}, + {"locale set to en", "en", true, "en"}, + {"locale set to ja_JP", "ja_JP", true, "ja_JP"}, + {"locale not set", "", false, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + viper.Reset() + + if tt.locale != "" { + viper.Set(config.DataRobotLocale, tt.locale) + } + + env := buildPluginEnv("/path/to/plugin", false) + + found := false + + for _, e := range env { + if len(e) > 10 && e[:10] == "DR_LOCALE=" { + found = true + + assert.Equal(t, "DR_LOCALE="+tt.expectedVal, e) + } + } + + assert.Equal(t, tt.expectEnv, found, + "DR_LOCALE presence mismatch: expected=%v, found=%v", tt.expectEnv, found) + }) + } +} + +func TestBuildPluginEnvAlwaysSetsPluginMode(t *testing.T) { + viper.Reset() + + env := buildPluginEnv("/path/to/plugin", false) + + found := false + + for _, e := range env { + if e == "DR_PLUGIN_MODE=1" { + found = true + } + } + + assert.True(t, found, "DR_PLUGIN_MODE=1 should always be set") +} + +func TestBuildPluginEnvSetsPluginPath(t *testing.T) { + viper.Reset() + + env := buildPluginEnv("/path/to/my-plugin", false) + + found := false + + for _, e := range env { + if e == "DR_PLUGIN_PATH=/path/to/my-plugin" { + found = true + } + } + + assert.True(t, found, "DR_PLUGIN_PATH should be set to the plugin path") +}