mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 22:24:31 +08:00
Compare commits
5 Commits
feat/sidec
...
feat/lang-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28d37fe080 | ||
|
|
5d63d1e2e7 | ||
|
|
73294b298f | ||
|
|
73f8b208ed | ||
|
|
645ae78b76 |
@@ -105,7 +105,7 @@ Interactive terminal use: run with no flags to enter the TUI form.`,
|
||||
cmd.Flags().StringVar(&opts.AppID, "app-id", "", "App ID to bind (required for OpenClaw multi-account)")
|
||||
cmd.Flags().StringVar(&opts.Identity, "identity", "", "identity preset (bot-only|user-default); defaults to bot-only in flag mode (safer: no impersonation)")
|
||||
cmd.Flags().BoolVar(&opts.Force, "force", false, "confirm a risky transition (currently: bot-only → user-default identity change in flag mode)")
|
||||
cmd.Flags().StringVar(&opts.Lang, "lang", "", "language preference (e.g. zh or zh_cn)")
|
||||
cmd.Flags().StringVar(&opts.Lang, "lang", "", "language preference (zh, en, or ja)")
|
||||
cmdutil.SetRisk(cmd, "write")
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -152,6 +152,8 @@ func TestConfigBindRun_InvalidLang(t *testing.T) {
|
||||
{"removed code ar", "ar"},
|
||||
{"unknown xx", "xx"},
|
||||
{"hyphen form zh-CN", "zh-CN"},
|
||||
{"dropped short code ko", "ko"},
|
||||
{"dropped locale ko_kr", "ko_kr"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@@ -225,6 +225,8 @@ func TestConfigInitCmd_InvalidLang(t *testing.T) {
|
||||
{"removed code ar", "ar"},
|
||||
{"unknown xx", "xx"},
|
||||
{"hyphen form zh-CN", "zh-CN"},
|
||||
{"dropped short code ko", "ko"},
|
||||
{"dropped locale ko_kr", "ko_kr"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -509,6 +511,18 @@ func TestValidateInitLang(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("dropped short code ko errors", func(t *testing.T) {
|
||||
opts := &ConfigInitOptions{Lang: "ko", langExplicit: true}
|
||||
if err := validateInitLang(opts); err == nil {
|
||||
t.Fatal("expected validation error for --lang ko, got nil")
|
||||
}
|
||||
})
|
||||
t.Run("dropped locale ko_kr errors", func(t *testing.T) {
|
||||
opts := &ConfigInitOptions{Lang: "ko_kr", langExplicit: true}
|
||||
if err := validateInitLang(opts); err == nil {
|
||||
t.Fatal("expected validation error for --lang ko_kr, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestPrintLangPreferenceConfirmation covers the confirmation helper: it prints
|
||||
|
||||
@@ -85,7 +85,7 @@ if the user explicitly wants a separate app inside the Agent workspace.`,
|
||||
cmd.Flags().StringVar(&opts.AppID, "app-id", "", "App ID (non-interactive)")
|
||||
cmd.Flags().BoolVar(&opts.AppSecretStdin, "app-secret-stdin", false, "Read App Secret from stdin to avoid process list exposure")
|
||||
cmd.Flags().StringVar(&opts.Brand, "brand", "feishu", "feishu or lark (non-interactive, default feishu)")
|
||||
cmd.Flags().StringVar(&opts.Lang, "lang", "", "language preference (e.g. zh or zh_cn)")
|
||||
cmd.Flags().StringVar(&opts.Lang, "lang", "", "language preference (zh, en, or ja)")
|
||||
cmd.Flags().StringVar(&opts.ProfileName, "name", "", "create or update a named profile (append instead of replace)")
|
||||
cmd.Flags().BoolVar(&opts.ForceInit, "force-init", false, "allow init inside an Agent workspace (OPENCLAW_HOME / HERMES_HOME); use config bind instead unless you really want a separate app")
|
||||
cmdutil.SetRisk(cmd, "write")
|
||||
|
||||
@@ -41,7 +41,7 @@ func NewCmdProfileAdd(f *cmdutil.Factory) *cobra.Command {
|
||||
cmd.Flags().StringVar(&appID, "app-id", "", "App ID (required)")
|
||||
cmd.Flags().BoolVar(&appSecretStdin, "app-secret-stdin", false, "read App Secret from stdin")
|
||||
cmd.Flags().StringVar(&brand, "brand", "feishu", "feishu or lark")
|
||||
cmd.Flags().StringVar(&lang, "lang", "", "language preference (e.g. zh or zh_cn)")
|
||||
cmd.Flags().StringVar(&lang, "lang", "", "language preference (zh, en, or ja)")
|
||||
cmd.Flags().BoolVar(&use, "use", false, "switch to this profile after adding")
|
||||
|
||||
_ = cmd.MarkFlagRequired("name")
|
||||
|
||||
@@ -100,6 +100,20 @@ func TestProfileAddRun_Lang(t *testing.T) {
|
||||
t.Fatalf("expected ExitValidation, got %T: %v", err, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dropped code ko errors", func(t *testing.T) {
|
||||
setupProfileConfigDir(t)
|
||||
f, _, _, _ := cmdutil.TestFactory(t, nil)
|
||||
f.IOStreams.In = strings.NewReader("secret\n")
|
||||
err := profileAddRun(f, "p", "app-p", true, "feishu", "ko", false)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for --lang ko, got nil")
|
||||
}
|
||||
exitErr, ok := err.(*output.ExitError)
|
||||
if !ok || exitErr.Code != output.ExitValidation {
|
||||
t.Fatalf("expected ExitValidation, got %T: %v", err, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProfileAddRun_UseAfterUpdatesCurrentAndPrevious(t *testing.T) {
|
||||
|
||||
@@ -10,17 +10,6 @@ const (
|
||||
LangZhCN Lang = "zh_cn"
|
||||
LangEnUS Lang = "en_us"
|
||||
LangJaJP Lang = "ja_jp"
|
||||
LangKoKR Lang = "ko_kr"
|
||||
LangFrFR Lang = "fr_fr"
|
||||
LangDeDE Lang = "de_de"
|
||||
LangEsES Lang = "es_es"
|
||||
LangItIT Lang = "it_it"
|
||||
LangRuRU Lang = "ru_ru"
|
||||
LangPtBR Lang = "pt_br"
|
||||
LangThTH Lang = "th_th"
|
||||
LangViVN Lang = "vi_vn"
|
||||
LangIdID Lang = "id_id"
|
||||
LangMsMY Lang = "ms_my"
|
||||
)
|
||||
|
||||
type langEntry struct {
|
||||
@@ -29,11 +18,13 @@ type langEntry struct {
|
||||
}
|
||||
|
||||
// catalog is the single source of truth; order drives --help and error listing.
|
||||
// Locked to {zh, en, ja} as of 2026-05-28: TUI bundles only ship for zh/en
|
||||
// (ja falls back to the zh bundle), and Lark API client code only branches on
|
||||
// these three for localization. Adding more entries here is meaningful only
|
||||
// after the downstream codepaths (mail signature locale, TUI bundle) gain
|
||||
// branches for them.
|
||||
var catalog = []langEntry{
|
||||
{LangZhCN, "zh"}, {LangEnUS, "en"}, {LangJaJP, "ja"}, {LangKoKR, "ko"},
|
||||
{LangFrFR, "fr"}, {LangDeDE, "de"}, {LangEsES, "es"}, {LangItIT, "it"},
|
||||
{LangRuRU, "ru"}, {LangPtBR, "pt"}, {LangThTH, "th"}, {LangViVN, "vi"},
|
||||
{LangIdID, "id"}, {LangMsMY, "ms"},
|
||||
{LangZhCN, "zh"}, {LangEnUS, "en"}, {LangJaJP, "ja"},
|
||||
}
|
||||
|
||||
// find matches a short code or Feishu locale against the catalog (case-sensitive).
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
package i18n
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -16,14 +19,17 @@ func TestParse(t *testing.T) {
|
||||
{"en", LangEnUS, true}, // short code
|
||||
{"en_us", LangEnUS, true}, // canonical locale
|
||||
{"ja", LangJaJP, true}, // short code
|
||||
{"pt", LangPtBR, true}, // pt → pt_br, not pt_pt
|
||||
{"ms", LangMsMY, true}, // ms → ms_my
|
||||
{"ja_jp", LangJaJP, true}, // canonical locale
|
||||
{"", "", false}, // unset
|
||||
{"ZH", "", false}, // case-sensitive
|
||||
{"zh-CN", "", false}, // hyphen form not accepted
|
||||
{"zh_CN", "", false}, // case-sensitive region
|
||||
{"ar", "", false}, // not in the supported set
|
||||
{"xx", "", false}, // unknown
|
||||
{"ko", "", false}, // dropped in 2026-05-28 catalog shrink
|
||||
{"ko_kr", "", false}, // dropped: legacy Feishu locale
|
||||
{"fr_fr", "", false}, // dropped: legacy Feishu locale
|
||||
{"de_de", "", false}, // dropped: legacy Feishu locale
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.in, func(t *testing.T) {
|
||||
@@ -81,11 +87,9 @@ func TestBase(t *testing.T) {
|
||||
|
||||
func TestCodes(t *testing.T) {
|
||||
codes := Codes()
|
||||
if len(codes) != 14 {
|
||||
t.Fatalf("len(Codes()) = %d, want 14", len(codes))
|
||||
}
|
||||
if codes[0] != "zh_cn" {
|
||||
t.Errorf("Codes()[0] = %q, want %q (catalog order)", codes[0], "zh_cn")
|
||||
want := []string{"zh_cn", "en_us", "ja_jp"}
|
||||
if !slices.Equal(codes, want) {
|
||||
t.Fatalf("Codes() = %v, want %v", codes, want)
|
||||
}
|
||||
// Every code must round-trip through Parse to itself (canonical).
|
||||
for _, c := range codes {
|
||||
|
||||
@@ -73,11 +73,20 @@ func (ctx *RuntimeContext) IsBot() bool {
|
||||
// UserOpenId returns the current user's open_id from config.
|
||||
func (ctx *RuntimeContext) UserOpenId() string { return ctx.Config.UserOpenId }
|
||||
|
||||
// Lang returns the user's preference as a canonical locale, or "" if unset or
|
||||
// unrecognized; callers choose their own fallback.
|
||||
// Lang returns the user's preference as a canonical locale.
|
||||
// Empty stays empty (unset). Any non-empty stored value that does not resolve
|
||||
// via i18n.Parse (e.g. legacy ko_kr / fr_fr from before the catalog was
|
||||
// shrunk to zh/en/ja) is silently coerced to LangZhCN — existing configs
|
||||
// stay readable, just behave as zh.
|
||||
func (ctx *RuntimeContext) Lang() i18n.Lang {
|
||||
lang, _ := i18n.Parse(string(ctx.Config.Lang))
|
||||
return lang
|
||||
raw := string(ctx.Config.Lang)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
if lang, ok := i18n.Parse(raw); ok {
|
||||
return lang
|
||||
}
|
||||
return i18n.LangZhCN
|
||||
}
|
||||
|
||||
// BotInfo holds bot identity metadata fetched lazily from /bot/v3/info.
|
||||
|
||||
@@ -20,7 +20,13 @@ func TestRuntimeContext_Lang(t *testing.T) {
|
||||
{"legacy short value normalizes", "ja", i18n.LangJaJP},
|
||||
{"legacy short zh normalizes", "zh", i18n.LangZhCN},
|
||||
{"unset stays empty", "", ""},
|
||||
{"unrecognized stays empty", "klingon", ""},
|
||||
// Flipped semantics: unrecognized non-empty values are now treated
|
||||
// as legacy storage from the pre-2026-05-28 14-language catalog
|
||||
// and silently coerced to LangZhCN, not left empty.
|
||||
{"unrecognized garbage coerces to zh", "klingon", i18n.LangZhCN},
|
||||
{"legacy ko_kr coerces to zh", "ko_kr", i18n.LangZhCN},
|
||||
{"legacy fr_fr coerces to zh", "fr_fr", i18n.LangZhCN},
|
||||
{"legacy short ko coerces to zh", "ko", i18n.LangZhCN},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestResolveLang(t *testing.T) {
|
||||
{"japanese", i18n.LangJaJP, "ja_jp"},
|
||||
{"chinese", i18n.LangZhCN, "zh_cn"},
|
||||
{"legacy short en", "en", "en_us"},
|
||||
{"unsupported-by-mail falls back to zh_cn", i18n.LangFrFR, "zh_cn"},
|
||||
{"legacy fr_fr falls back to zh_cn", i18n.Lang("fr_fr"), "zh_cn"},
|
||||
{"unset falls back to zh_cn", "", "zh_cn"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user