snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

1
tools/mmgotool/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/bin/

View File

@@ -0,0 +1,8 @@
version: "2"
linters:
exclusions:
rules:
- linters:
- staticcheck
text: QF1003

15
tools/mmgotool/Makefile Normal file
View File

@@ -0,0 +1,15 @@
.PHONY: all test golangci-lint check-style
export GOBIN ?= $(PWD)/bin
GO ?= go
all: test
test:
$(GO) test ./...
golangci-lint:
$(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
$(GOBIN)/golangci-lint run ./...
check-style: golangci-lint

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,344 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// writeCatalog writes one locale file into dir and returns its path.
func writeCatalog(t *testing.T, dir, name, body string) string {
t.Helper()
p := filepath.Join(dir, name)
require.NoError(t, os.WriteFile(p, []byte(body), 0600))
return p
}
// source parses an en.json body into the items a catalog is checked against.
func source(t *testing.T, en string) map[string]Item {
t.Helper()
items, err := loadItems([]byte(en))
require.NoError(t, err)
return items
}
func TestVerifyLocale(t *testing.T) {
t.Parallel()
// A pluralized source and a plain source, so each case can pick the shape
// it needs without restating the catalog.
const enPlural = `[{"id":"a.b","translation":{"one":"{{.User}} is here","other":"{{.User}} are here"}}]`
const enPlain = `[{"id":"a.b","translation":"{{.User}} is here"}]`
testCases := []struct {
name string
localeName string
en string
locale string
warnMissingIDs bool
problem string // substring expected in problems, "" for none
warning string // substring expected in warnings, "" for none
}{
{
name: "clean plain catalog",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":"{{.User}} est ici"}]`,
},
{
name: "clean pluralized catalog",
localeName: "fr.json",
en: enPlural,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} est ici","other":"{{.User}} sont ici"}}]`,
},
// The runtime loader is the first gate: whatever it rejects at server
// startup has to be rejected here.
{
name: "loader rejects invalid JSON",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b",`,
problem: "rejected by the runtime translation loader",
},
{
name: "loader rejects an unparseable template",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":"{{.User est ici"}]`,
problem: "rejected by the runtime translation loader",
},
// Id parity.
{
name: "missing id is an error by default",
localeName: "fr.json",
en: enPlain,
locale: `[]`,
problem: "a.b: missing id",
},
{
name: "missing id is a warning under warn-missing-ids",
localeName: "fr.json",
en: enPlain,
locale: `[]`,
warnMissingIDs: true,
warning: "a.b: missing id",
},
{
name: "extra id not in en.json",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":"{{.User}} est ici"},{"id":"z.z","translation":"orphelin"}]`,
problem: "z.z: extra id not in en.json",
},
// Template tokens. An unknown one renders "<no value>"; a dropped one
// quietly loses the value it was meant to show.
{
name: "unknown template token",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":"{{.User}} est ici, {{.Extra}}"}]`,
problem: `unknown template token {{.Extra}} not present in source`,
},
{
name: "dropped template token",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":"quelqu'un est ici"}]`,
problem: `source template token {{.User}} is missing from the translation`,
},
{
name: "a token in any plural form counts as present",
localeName: "fr.json",
en: enPlural,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} est ici","other":"ils sont ici"}}]`,
},
// Plural shape against en.json.
{
name: "demoting a pluralized source to a single string",
localeName: "fr.json",
en: enPlural,
locale: `[{"id":"a.b","translation":"{{.User}} sont ici"}]`,
problem: "en.json pluralises this id but the translation is a single string",
},
{
name: "promoting a plain source to a plural is allowed",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} est ici","other":"{{.User}} sont ici"}}]`,
},
{
name: "categories are checked on a promoted plural too",
localeName: "fr.json",
en: enPlain,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} est ici","many":"x","other":"{{.User}} sont ici"}}]`,
problem: `plural category "many" is not used by this locale`,
},
// CLDR categories for the locale.
{
name: "missing a category the locale requires",
localeName: "ro.json",
en: enPlural,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} e aici","other":"{{.User}} sunt aici"}}]`,
problem: `missing plural category "few"`,
},
{
name: "a category the locale does not use",
localeName: "ja.json",
en: enPlural,
locale: `[{"id":"a.b","translation":{"one":"{{.User}}","other":"{{.User}}"}}]`,
problem: `plural category "one" is not used by this locale`,
},
// An empty form renders the raw translation id, so it is worse than no
// translation at all.
{
name: "empty plural form",
localeName: "fr.json",
en: enPlural,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} est ici","other":""}}]`,
problem: `plural category "other" is empty, which renders the raw translation id`,
},
{
name: "whitespace-only plural form",
localeName: "fr.json",
en: enPlural,
locale: `[{"id":"a.b","translation":{"one":"{{.User}} est ici","other":" "}}]`,
problem: `plural category "other" is empty, which renders the raw translation id`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
problems, warnings := verifyLocale(tc.localeName, []byte(tc.locale), source(t, tc.en), tc.warnMissingIDs)
if tc.problem == "" {
assert.Empty(t, problems, "expected no problems")
} else {
assert.Contains(t, strings.Join(problems, "\n"), tc.problem)
}
if tc.warning == "" {
assert.Empty(t, warnings, "expected no warnings")
} else {
assert.Contains(t, strings.Join(warnings, "\n"), tc.warning)
}
})
}
}
// The runtime loader rejects these before any of the catalog checks run, which
// is what makes the "no CLDR plural spec" branch unreachable: it derives the
// language with language.Parse, which only yields one when a plural spec exists
// for the same tag.
func TestVerifyLocaleUnknownLocale(t *testing.T) {
t.Parallel()
const catalog = `[{"id":"a.b","translation":"hi"}]`
for _, name := range []string{"xx.json", "notes.json", "README.json"} {
t.Run(name, func(t *testing.T) {
t.Parallel()
problems, _ := verifyLocale(name, []byte(catalog), source(t, catalog), false)
assert.Contains(t, strings.Join(problems, "\n"), "rejected by the runtime translation loader")
})
}
}
// Each file is loaded into its own bundle, so a defect in one locale can never
// be masked by another having already been loaded, in either order.
func TestVerifyLocaleIsOrderIndependent(t *testing.T) {
t.Parallel()
const en = `[{"id":"a.b","translation":{"one":"{{.User}} is here","other":"{{.User}} are here"}}]`
const good = `[{"id":"a.b","translation":{"one":"{{.User}} ist da","other":"{{.User}} sind da"}}]`
const bad = `[{"id":"a.b","translation":{"one":"{{.User}} est ici","other":""}}]`
items := source(t, en)
badFirst, _ := verifyLocale("fr.json", []byte(bad), items, false)
_, _ = verifyLocale("de.json", []byte(good), items, false)
badSecond, _ := verifyLocale("fr.json", []byte(bad), items, false)
assert.NotEmpty(t, badFirst)
assert.Equal(t, badFirst, badSecond, "the same file must report the same problems regardless of what was checked before it")
}
func TestVerifyCmd(t *testing.T) {
t.Parallel()
// setup builds a server-dir whose i18n/ holds en.json, one locale, and the
// entries the walk is meant to ignore.
setup := func(t *testing.T, locale string) string {
t.Helper()
serverDir := t.TempDir()
dir := filepath.Join(serverDir, "i18n")
require.NoError(t, os.MkdirAll(dir, 0700))
writeCatalog(t, dir, "en.json", `[{"id":"a.b","translation":"{{.User}} is here"}]`)
writeCatalog(t, dir, "fr.json", locale)
// Neither of these is a locale catalog, and neither may be walked.
writeCatalog(t, dir, "README.md", "not a catalog")
require.NoError(t, os.MkdirAll(filepath.Join(dir, "nested"), 0700))
writeCatalog(t, filepath.Join(dir, "nested"), "de.json", `[{"id":"nope","translation":"x"}]`)
return serverDir
}
run := func(t *testing.T, serverDir string, args ...string) error {
t.Helper()
cmd := *VerifyCmd
cmd.SetArgs(args)
cmd.SetOut(os.NewFile(0, os.DevNull))
return verifyCmdF(&cmd, nil)
}
t.Run("a clean catalog passes, and non-catalog entries are skipped", func(t *testing.T) {
t.Parallel()
serverDir := setup(t, `[{"id":"a.b","translation":"{{.User}} est ici"}]`)
require.NoError(t, VerifyCmd.Flags().Set("server-dir", serverDir))
require.NoError(t, VerifyCmd.Flags().Set("warn-missing-ids", "false"))
// A walked README.md or nested/de.json would surface as an extra id.
assert.NoError(t, run(t, serverDir))
})
t.Run("a defect fails the command", func(t *testing.T) {
serverDir := setup(t, `[{"id":"a.b","translation":"{{.Nope}} est ici"}]`)
require.NoError(t, VerifyCmd.Flags().Set("server-dir", serverDir))
require.NoError(t, VerifyCmd.Flags().Set("warn-missing-ids", "false"))
err := run(t, serverDir)
require.Error(t, err)
assert.Contains(t, err.Error(), "error(s) across 1 locale files")
})
t.Run("a stray json file is reported", func(t *testing.T) {
serverDir := setup(t, `[{"id":"a.b","translation":"{{.User}} est ici"}]`)
writeCatalog(t, filepath.Join(serverDir, "i18n"), "notes.json", "{not even json")
require.NoError(t, VerifyCmd.Flags().Set("server-dir", serverDir))
require.NoError(t, VerifyCmd.Flags().Set("warn-missing-ids", "false"))
err := run(t, serverDir)
require.Error(t, err)
assert.Contains(t, err.Error(), "error(s) across 2 locale files")
})
t.Run("a source catalog the loader rejects fails the command", func(t *testing.T) {
serverDir := t.TempDir()
dir := filepath.Join(serverDir, "i18n")
require.NoError(t, os.MkdirAll(dir, 0700))
writeCatalog(t, dir, "en.json", `[{"id":"a.b","translation":"{{.User is here"}]`)
writeCatalog(t, dir, "fr.json", `[{"id":"a.b","translation":"{{.User}} est ici"}]`)
require.NoError(t, VerifyCmd.Flags().Set("server-dir", serverDir))
require.NoError(t, VerifyCmd.Flags().Set("warn-missing-ids", "false"))
err := run(t, serverDir)
require.Error(t, err)
assert.Contains(t, err.Error(), "rejected by the runtime translation loader")
})
// The symlink target is a loadable catalog, so root is the only thing that
// can fail this.
t.Run("a catalog symlinked out of the translation directory is refused", func(t *testing.T) {
serverDir := setup(t, `[{"id":"a.b","translation":"{{.User}} est ici"}]`)
outside := writeCatalog(t, t.TempDir(), "de.json", `[{"id":"a.b","translation":"{{.User}} ist da"}]`)
require.NoError(t, os.Symlink(outside, filepath.Join(serverDir, "i18n", "de.json")))
require.NoError(t, VerifyCmd.Flags().Set("server-dir", serverDir))
require.NoError(t, VerifyCmd.Flags().Set("warn-missing-ids", "false"))
err := run(t, serverDir)
require.Error(t, err)
assert.Contains(t, err.Error(), "error(s) across 2 locale files")
})
t.Run("a missing translation directory is reported against the path", func(t *testing.T) {
require.NoError(t, VerifyCmd.Flags().Set("server-dir", filepath.Join(t.TempDir(), "nope")))
require.NoError(t, VerifyCmd.Flags().Set("warn-missing-ids", "false"))
err := run(t, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to open translation directory")
})
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/spf13/cobra"
)
type Command = cobra.Command
func Run(args []string) error {
RootCmd.SetArgs(args)
return RootCmd.Execute()
}
var RootCmd = &cobra.Command{
Use: "mmgotool",
Short: "Mattermost dev utils cli",
Long: `Mattermost cli to help in the development process`,
}

19
tools/mmgotool/go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/mattermost/mattermost/tools/mmgotool
go 1.26.7
require (
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.10.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

29
tools/mmgotool/go.sum Normal file
View File

@@ -0,0 +1,29 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

16
tools/mmgotool/main.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"os"
"github.com/mattermost/mattermost/tools/mmgotool/commands"
)
func main() {
if err := commands.Run(os.Args[1:]); err != nil {
os.Exit(1)
}
}