Skip to content

Commit ac69411

Browse files
committed
Add policy-aware check command
Extend `check` with `--policy=experimental|shipping`. Experimental requires: valid manifest, locale present, no stale keys, validation passing, and coherent metadata. Shipping adds: no missing keys and no drifted keys. The original check behavior (without --policy) is unchanged. Signed-off-by: Jan Dubois <jan.dubois@suse.com>
1 parent e047f1b commit ac69411

2 files changed

Lines changed: 254 additions & 7 deletions

File tree

src/go/i18n-report/report_check.go

Lines changed: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,32 @@ import (
88
func runCheck(args []string) error {
99
fs := flag.NewFlagSet("check", flag.ExitOnError)
1010
locale := fs.String("locale", "", "Target locale code (required)")
11+
policy := fs.String("policy", "", "Policy level: experimental, shipping (optional)")
1112
fs.Parse(args)
1213

1314
if *locale == "" {
1415
return fmt.Errorf("--locale is required")
1516
}
1617

18+
if *policy != "" && *policy != "experimental" && *policy != "shipping" {
19+
return fmt.Errorf("--policy must be experimental or shipping")
20+
}
21+
1722
root, err := repoRoot()
1823
if err != nil {
1924
return err
2025
}
2126

27+
if *policy != "" {
28+
return reportCheckPolicy(root, *locale, *policy)
29+
}
30+
return reportCheckBasic(root, *locale)
31+
}
32+
33+
// reportCheckBasic is the original check: unused, stale, missing counts.
34+
func reportCheckBasic(root, locale string) error {
2235
enPath := translationsPath(root, "en-us.yaml")
23-
localePath := translationsPath(root, *locale+".yaml")
36+
localePath := translationsPath(root, locale+".yaml")
2437

2538
enKeys, err := loadYAMLFlat(enPath)
2639
if err != nil {
@@ -36,31 +49,27 @@ func runCheck(args []string) error {
3649
return err
3750
}
3851

39-
// Count unused keys.
4052
unusedCount := 0
4153
for k := range enKeys {
4254
if _, found := refs[k]; !found {
4355
unusedCount++
4456
}
4557
}
4658

47-
// Count stale keys.
4859
staleCount := 0
4960
for k := range localeKeys {
5061
if _, found := enKeys[k]; !found {
5162
staleCount++
5263
}
5364
}
5465

55-
// Count keys missing from locale.
5666
missingCount := 0
5767
for k := range enKeys {
5868
if _, found := localeKeys[k]; !found {
5969
missingCount++
6070
}
6171
}
6272

63-
// Print results.
6473
passed := true
6574
printResult := func(label string, count int) {
6675
status := "OK"
@@ -72,12 +81,148 @@ func runCheck(args []string) error {
7281
}
7382

7483
printResult("unused keys", unusedCount)
75-
printResult("stale keys in "+*locale, staleCount)
76-
printResult("keys missing from "+*locale, missingCount)
84+
printResult("stale keys in "+locale, staleCount)
85+
printResult("keys missing from "+locale, missingCount)
7786

7887
if passed {
7988
fmt.Println("All checks passed.")
8089
return nil
8190
}
8291
return fmt.Errorf("checks failed")
8392
}
93+
94+
// reportCheckPolicy runs policy-aware checks on a locale.
95+
//
96+
// Experimental policy:
97+
// - manifest valid
98+
// - locale file present
99+
// - no stale keys
100+
// - validate passes
101+
// - metadata coherent
102+
//
103+
// Shipping policy (all of the above plus):
104+
// - no missing keys
105+
// - no drifted keys
106+
func reportCheckPolicy(root, locale, policy string) error {
107+
passed := true
108+
printResult := func(label string, ok bool, detail string) {
109+
status := "OK"
110+
if !ok {
111+
status = "FAIL"
112+
passed = false
113+
}
114+
if detail != "" {
115+
fmt.Printf(" %-35s %s %s\n", label+":", status, detail)
116+
} else {
117+
fmt.Printf(" %-35s %s\n", label+":", status)
118+
}
119+
}
120+
121+
fmt.Printf("Policy check (%s) for %s:\n", policy, locale)
122+
123+
// Manifest valid and locale status matches policy.
124+
m, manifestErr := loadManifest(root)
125+
printResult("manifest valid", manifestErr == nil, errString(manifestErr))
126+
if manifestErr == nil {
127+
localeInfo, registered := m.Locales[locale]
128+
if !registered {
129+
printResult("locale registered in manifest", false, "not found in meta/locales.yaml")
130+
} else if policy == "shipping" && localeInfo.Status != StatusShipping && localeInfo.Status != StatusSource {
131+
printResult("locale status matches policy", false,
132+
fmt.Sprintf("locale status is %q, shipping policy requires \"shipping\" or \"source\"", localeInfo.Status))
133+
}
134+
}
135+
136+
// Locale file present.
137+
localePath := translationsPath(root, locale+".yaml")
138+
enPath := translationsPath(root, "en-us.yaml")
139+
140+
enKeys, err := loadYAMLFlat(enPath)
141+
if err != nil {
142+
return err
143+
}
144+
145+
localeKeys, localeErr := loadYAMLFlat(localePath)
146+
printResult("locale file readable", localeErr == nil, errString(localeErr))
147+
if localeErr != nil {
148+
localeKeys = make(map[string]string)
149+
}
150+
151+
// No stale keys.
152+
staleCount := 0
153+
for k := range localeKeys {
154+
if _, found := enKeys[k]; !found {
155+
staleCount++
156+
}
157+
}
158+
printResult("no stale keys", staleCount == 0, countDetail(staleCount))
159+
160+
// Validate passes.
161+
validateErr := reportValidateQuiet(root, locale)
162+
printResult("validate passes", validateErr == nil, errString(validateErr))
163+
164+
// Load metadata for the drift check below. Metadata coherence is already
165+
// covered by the "validate passes" check above (validateLocale checks it).
166+
meta, _ := loadMetadata(root, locale)
167+
168+
// Shipping-only checks.
169+
if policy == "shipping" {
170+
// No missing keys.
171+
missingCount := 0
172+
for k := range enKeys {
173+
if _, found := localeKeys[k]; !found {
174+
missingCount++
175+
}
176+
}
177+
printResult("no missing keys", missingCount == 0, countDetail(missingCount))
178+
179+
// No drifted keys.
180+
if meta != nil {
181+
driftCount := 0
182+
for k := range localeKeys {
183+
enValue, inEn := enKeys[k]
184+
storedSource, inMeta := meta[k]
185+
if !inEn || !inMeta {
186+
continue
187+
}
188+
if enValue != storedSource {
189+
driftCount++
190+
}
191+
}
192+
printResult("no drifted keys", driftCount == 0, countDetail(driftCount))
193+
}
194+
}
195+
196+
if passed {
197+
fmt.Printf("All %s policy checks passed for %s.\n", policy, locale)
198+
return nil
199+
}
200+
return fmt.Errorf("%s policy checks failed for %s", policy, locale)
201+
}
202+
203+
// reportValidateQuiet runs validate and returns an error summary without
204+
// printing individual errors.
205+
func reportValidateQuiet(root, locale string) error {
206+
errors, err := validateLocale(root, locale)
207+
if err != nil {
208+
return err
209+
}
210+
if len(errors) > 0 {
211+
return fmt.Errorf("%d validation errors", len(errors))
212+
}
213+
return nil
214+
}
215+
216+
func errString(err error) string {
217+
if err == nil {
218+
return ""
219+
}
220+
return err.Error()
221+
}
222+
223+
func countDetail(count int) string {
224+
if count == 0 {
225+
return ""
226+
}
227+
return fmt.Sprintf("%d found", count)
228+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func setupCheckTestRepo(t *testing.T, enUS, locale string, withMeta bool, localeStatus ...string) string {
10+
t.Helper()
11+
dir := t.TempDir()
12+
transDir := filepath.Join(dir, "pkg", "rancher-desktop", "assets", "translations")
13+
metaDir := filepath.Join(transDir, "meta")
14+
os.MkdirAll(metaDir, 0755)
15+
os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0644)
16+
os.WriteFile(filepath.Join(transDir, "en-us.yaml"), []byte(enUS), 0644)
17+
os.WriteFile(filepath.Join(transDir, "de.yaml"), []byte(locale), 0644)
18+
19+
status := "experimental"
20+
if len(localeStatus) > 0 {
21+
status = localeStatus[0]
22+
}
23+
manifest := "locales:\n en-us:\n status: source\n de:\n status: " + status + "\n"
24+
os.WriteFile(filepath.Join(metaDir, "locales.yaml"), []byte(manifest), 0644)
25+
26+
if withMeta {
27+
generateMetadata(dir, "de")
28+
}
29+
return dir
30+
}
31+
32+
func TestCheckPolicyExperimentalPasses(t *testing.T) {
33+
enUS := "status:\n checking: Checking...\n done: Done\n"
34+
// de has only 1 key — missing keys are OK for experimental.
35+
de := "status:\n checking: Wird geprüft…\n"
36+
dir := setupCheckTestRepo(t, enUS, de, true)
37+
38+
err := reportCheckPolicy(dir, "de", "experimental")
39+
if err != nil {
40+
t.Errorf("experimental should pass with missing keys, got: %v", err)
41+
}
42+
}
43+
44+
func TestCheckPolicyShippingFailsMissing(t *testing.T) {
45+
enUS := "status:\n checking: Checking...\n done: Done\n"
46+
de := "status:\n checking: Wird geprüft…\n"
47+
dir := setupCheckTestRepo(t, enUS, de, true, "shipping")
48+
49+
err := reportCheckPolicy(dir, "de", "shipping")
50+
if err == nil {
51+
t.Error("shipping should fail with missing keys")
52+
}
53+
}
54+
55+
func TestCheckPolicyShippingPasses(t *testing.T) {
56+
enUS := "status:\n checking: Checking...\n done: Done\n"
57+
de := "status:\n checking: Wird geprüft…\n done: Fertig\n"
58+
dir := setupCheckTestRepo(t, enUS, de, true, "shipping")
59+
60+
err := reportCheckPolicy(dir, "de", "shipping")
61+
if err != nil {
62+
t.Errorf("shipping should pass with complete translation, got: %v", err)
63+
}
64+
}
65+
66+
func TestCheckPolicyShippingFailsExperimentalStatus(t *testing.T) {
67+
enUS := "status:\n checking: Checking...\n"
68+
de := "status:\n checking: Wird geprüft…\n"
69+
dir := setupCheckTestRepo(t, enUS, de, true, "experimental")
70+
71+
err := reportCheckPolicy(dir, "de", "shipping")
72+
if err == nil {
73+
t.Error("shipping should fail for experimental-status locale")
74+
}
75+
}
76+
77+
func TestCheckPolicyExperimentalFailsStale(t *testing.T) {
78+
enUS := "status:\n checking: Checking...\n"
79+
// de has a stale key not in en-us.
80+
de := "status:\n checking: Wird geprüft…\n removed: Veraltet\n"
81+
dir := setupCheckTestRepo(t, enUS, de, true)
82+
83+
err := reportCheckPolicy(dir, "de", "experimental")
84+
if err == nil {
85+
t.Error("experimental should fail with stale keys")
86+
}
87+
}
88+
89+
func TestCheckPolicyShippingFailsDrift(t *testing.T) {
90+
enUS := "status:\n checking: Checking...\n"
91+
de := "status:\n checking: Wird geprüft…\n"
92+
dir := setupCheckTestRepo(t, enUS, de, true, "shipping")
93+
94+
// Change English after metadata was generated.
95+
transDir := filepath.Join(dir, "pkg", "rancher-desktop", "assets", "translations")
96+
os.WriteFile(filepath.Join(transDir, "en-us.yaml"), []byte("status:\n checking: Verifying...\n"), 0644)
97+
98+
err := reportCheckPolicy(dir, "de", "shipping")
99+
if err == nil {
100+
t.Error("shipping should fail with drifted keys")
101+
}
102+
}

0 commit comments

Comments
 (0)