refactor(magefile): consolidate api+frontend translation checks into one task

Previously the PR introduced a separate `check:frontendTranslations` mage
task and a second CI job. Merge both into the existing `check:translations`
task and a single CI job. Also rename internal references from "backend" to
"api" to match the project convention (Vikunja's Go server is the api, not
the backend).
This commit is contained in:
kolaente 2026-04-23 12:49:41 +02:00
parent edd83f5e92
commit 1d637a4ac6
2 changed files with 38 additions and 41 deletions

View File

@ -78,7 +78,7 @@ jobs:
with:
version: v2.10.1
api-check-translations:
check-translations:
runs-on: ubuntu-latest
needs: mage
steps:
@ -92,20 +92,6 @@ jobs:
chmod +x ./mage-static
./mage-static check:translations
frontend-check-translations:
runs-on: ubuntu-latest
needs: mage
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Download Mage Binary
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: mage_bin
- name: Check
run: |
chmod +x ./mage-static
./mage-static check:frontendTranslations
test-migration-smoke:
runs-on: ubuntu-latest
needs:

View File

@ -665,53 +665,65 @@ func (Check) GotSwag(ctx context.Context) error {
return nil
}
// backendDynamicKeyPrefixes lists prefixes that are used dynamically (with a
// non-literal key argument) in the backend. Any translation key starting with
// apiDynamicKeyPrefixes lists prefixes that are used dynamically (with a
// non-literal key argument) in the api. Any translation key starting with
// one of these prefixes is considered "used" so the dead-key detection doesn't
// false-positive on them. Add a prefix here only after verifying the dynamic
// call site actually produces the expected keys.
var backendDynamicKeyPrefixes = []string{
var apiDynamicKeyPrefixes = []string{
// pkg/utils/humanize_duration.go uses i18n.TP(lang, chunk.key, ...) where
// chunk.key comes from a struct containing the time.since_* keys.
"time.since_",
}
// Translations checks if all translation keys used in the code exist in the
// English translation file, and conversely that no unused keys exist in the
// translation file.
// Translations checks that all translation keys used in the code exist in
// their respective English translation files, and conversely that no unused
// keys exist in those files. Both the api (Go) and the frontend (Vue/TS) are
// checked in one run so a single CI job can gate the whole repository.
func (Check) Translations() error {
mg.Deps(initVars)
fmt.Println("Checking backend translation keys...")
var errs []error
if err := checkAPITranslations(); err != nil {
errs = append(errs, err)
}
if err := checkFrontendTranslations(); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func checkAPITranslations() error {
fmt.Println("Checking api translation keys...")
translationFile := "./pkg/i18n/lang/en.json"
translations, err := loadTranslations(translationFile)
if err != nil {
return fmt.Errorf("error loading translations: %w", err)
return fmt.Errorf("error loading api translations: %w", err)
}
fmt.Printf("Loaded %d translation keys from %s\n", len(translations), translationFile)
keys, err := walkBackendForTranslationKeys(".")
keys, err := walkAPIForTranslationKeys(".")
if err != nil {
return fmt.Errorf("error walking codebase: %w", err)
return fmt.Errorf("error walking api codebase: %w", err)
}
fmt.Printf("Found %d translation key references in the backend codebase\n", len(keys))
fmt.Printf("Found %d translation key references in the api codebase\n", len(keys))
return reportTranslationResults("backend", translations, keys, backendDynamicKeyPrefixes)
return reportTranslationResults("api", translations, keys, apiDynamicKeyPrefixes)
}
// FrontendTranslations checks that all translation keys used in the frontend
// exist in the frontend English translation file, and that no unused keys
// exist in the translation file.
func (Check) FrontendTranslations() error {
mg.Deps(initVars)
func checkFrontendTranslations() error {
fmt.Println("Checking frontend translation keys...")
translationFile := "./frontend/src/i18n/lang/en.json"
translations, err := loadTranslations(translationFile)
if err != nil {
return fmt.Errorf("error loading translations: %w", err)
return fmt.Errorf("error loading frontend translations: %w", err)
}
fmt.Printf("Loaded %d translation keys from %s\n", len(translations), translationFile)
@ -852,11 +864,11 @@ func flattenTranslations(prefix string, src map[string]any, dest map[string]bool
}
}
// walkBackendForTranslationKeys walks the Go backend and extracts translation
// walkAPIForTranslationKeys walks the Go api code and extracts translation
// keys referenced via string-literal arguments to i18n.T / i18n.TP. Dynamic
// (non-literal) references are not detected here add them to
// backendDynamicKeyPrefixes instead.
func walkBackendForTranslationKeys(rootDir string) ([]TranslationKey, error) {
// apiDynamicKeyPrefixes instead.
func walkAPIForTranslationKeys(rootDir string) ([]TranslationKey, error) {
var allKeys []TranslationKey
pkgDir := filepath.Join(rootDir, "pkg")
@ -871,7 +883,7 @@ func walkBackendForTranslationKeys(rootDir string) ([]TranslationKey, error) {
}
if !info.IsDir() && strings.HasSuffix(path, ".go") {
keys, err := extractBackendTranslationKeysFromFile(path)
keys, err := extractAPITranslationKeysFromFile(path)
if err != nil {
fmt.Printf("Warning: %v\n", err)
return nil
@ -885,10 +897,10 @@ func walkBackendForTranslationKeys(rootDir string) ([]TranslationKey, error) {
return allKeys, err
}
// extractBackendTranslationKeysFromFile extracts all i18n.T/i18n.TP calls with
// extractAPITranslationKeysFromFile extracts all i18n.T/i18n.TP calls with
// a string-literal key. Non-literal (dynamic) keys are not flagged; any
// dynamic-key reference must be allowlisted via backendDynamicKeyPrefixes.
func extractBackendTranslationKeysFromFile(filePath string) ([]TranslationKey, error) {
// dynamic-key reference must be allowlisted via apiDynamicKeyPrefixes.
func extractAPITranslationKeysFromFile(filePath string) ([]TranslationKey, error) {
content, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("error reading file %s: %w", filePath, err)
@ -1102,7 +1114,6 @@ func (Check) All() {
Check.Golangci,
Check.GotSwag,
Check.Translations,
Check.FrontendTranslations,
)
}