Skip to content
  •  
  •  
  •  
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ because it turns other people's test suites red.

### Added

- **The website in twenty-two languages.** Beside English and Polish it now
reads in Simplified and Traditional Chinese, Japanese, German, French,
Spanish, Brazilian Portuguese, Italian, Indonesian, Russian, Turkish, Czech,
Vietnamese, Hindi, Korean, Arabic, Romanian, Dutch, Ukrainian and Thai.
Every page exists in every language, with the same facts: the numbers come
from the program, the same as before. A menu in the header lists the
languages by the names they call themselves. Arabic reads from right to
left, and commands and code stay left to right in it. Each page tells a
search engine which language it is in and where its translations are, so
somebody searching in their own language is sent to the page in it. The
pages of Latin-script languages have addresses in the language
(`/de/dokumentation/`), the others keep the English words under the
language prefix (`/ja/docs/`). The window and the command line are not
part of this.
- **Two guides on the website.** How to make a corrupt file for testing, with
the damages listed from the program itself, and how to generate test files in
a CI pipeline, with a GitHub Actions workflow, a GitLab job and the
PowerShell catch. Both are in every language of the site.
- **A Tools tab, and `tfg tool`.** Small things to do with files you already
have, in the window and on the command line with the same settings. The
first works out the checksum of a file - md5, sha1, sha256, sha512 or crc32,
Expand Down Expand Up @@ -76,6 +94,9 @@ because it turns other people's test suites red.

### Fixed

- **The FAQ on the website said a deliberately broken file was not possible
yet.** `tfg damage` has been in the tool since 0.3.0, and the answer still
called it a planned feature. It now says how to make one, in every language.
- **The window no longer offers to write into a folder it cannot or should
not write into.** Started from Finder on macOS it offered `/tfg-out`,
which is read-only, so the first run ended in a refusal from the system.
Expand Down
81 changes: 64 additions & 17 deletions internal/guard/edgeexample_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import (
// The three files every first page shows - one byte under a 1 MB limit, the
// limit, one byte over - are what size-boundaries actually writes.
//
// They stand in four places a stranger reads before anything else: the social
// card, the README, and the site's first page in both languages. All four
// carry them as literals, and the card says outright that everything on it is
// real. Nothing asked the program. An outside review of #148 named it, and
// They stand in the places a stranger reads before anything else: the social
// card, the README, and the site's first page in every language. All of them
// carry the files as literals, and the card says outright that everything on it
// is real. Nothing asked the program. An outside review of #148 named it, and
// asking showed it was already half untrue: the card before that one printed
// "tfg generate --preset size-boundaries --limit 1mb", which the program
// refuses, because the default spread would need a file of 0 B.
Expand Down Expand Up @@ -52,48 +52,64 @@ func TestTheLimitExampleIsWhatThePresetWrites(t *testing.T) {
}

root := repoRoot(t)
places := []struct {
type place struct {
file string
spaced bool
outcomes map[string]string
command bool
}{
}
places := []place{
{"web/templates/social.html", true, map[string]string{"accept": "accept", "reject": "reject"}, false},
{"README.md", false, map[string]string{"accept": "**accept**", "reject": "**reject**"}, true},
{"web/content/en/index.html", false, map[string]string{"accept": "accept", "reject": "reject"}, true},
{"web/content/pl/index.html", false, map[string]string{"accept": "przyjąć", "reject": "odrzucić"}, true},
}
for _, place := range places {
body, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(place.file)))
// Every other translation of the first page is held to the same answer. The
// words are asked for rather than guessed: a language whose first page shows
// the three files in a form nobody listed here is a page this guard cannot
// read, and silence about it would be the defect the guard exists for.
for _, l := range languagesOnDisk(t) {
if l.Code == "en" || l.Code == "pl" {
continue
}
words, ok := translatedLimitWords[l.Code]
if !ok {
t.Errorf("the first page in %s is not held to the limit example - add the words it uses for accept and reject to translatedLimitWords", l.Code)
continue
}
places = append(places, place{"web/content/" + l.Code + "/index.html", false, map[string]string{"accept": words[0], "reject": words[1]}, true})
}
for _, at := range places {
body, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(at.file)))
if err != nil {
t.Fatalf("reading %s: %v", place.file, err)
t.Fatalf("reading %s: %v", at.file, err)
}
text := string(body)
if place.command && !strings.Contains(text, command) {
if at.command && !strings.Contains(text, command) {
t.Errorf("%s no longer prints %q, so the files it shows come from a command this guard does not run",
place.file, command)
at.file, command)
}
for _, f := range manifest.Files {
line := lineNaming(text, f.Name)
if line == "" {
t.Errorf("%s does not show %s, and the command writes it", place.file, f.Name)
t.Errorf("%s does not show %s, and the command writes it", at.file, f.Name)
continue
}
size := strconv.FormatInt(f.Bytes, 10)
if place.spaced {
if at.spaced {
size = spacedBytes(f.Bytes) + " B"
}
if !strings.Contains(line, size) {
t.Errorf("%s shows %s without its %s:\n%s", place.file, f.Name, size, line)
t.Errorf("%s shows %s without its %s:\n%s", at.file, f.Name, size, line)
}
word, ok := place.outcomes[f.Expected.Outcome]
word, ok := at.outcomes[f.Expected.Outcome]
if !ok {
t.Errorf("the manifest declares %q for %s, and %s has no word for it", f.Expected.Outcome, f.Name, place.file)
t.Errorf("the manifest declares %q for %s, and %s has no word for it", f.Expected.Outcome, f.Name, at.file)
continue
}
if !strings.Contains(line, word) {
t.Errorf("%s shows %s without the outcome the manifest declares (%s):\n%s",
place.file, f.Name, word, line)
at.file, f.Name, word, line)
}
}
}
Expand Down Expand Up @@ -128,3 +144,34 @@ func spacedBytes(n int64) string {
}
return b.String()
}

// translatedLimitWords are the two words each translated first page uses for
// what the manifest declares - accept first, reject second - in the form that
// stands on the line naming a file.
//
// A word is not unique to a language, and a translation inflects it. Polish
// writes the infinitive in the table and so the list says przyjąć rather than
// przyjmie, and each entry is exactly the form the page shows. The key is the
// language tag, the same as the directory under web/content.
var translatedLimitWords = map[string][2]string{
"de": {"akzeptieren", "ablehnen"},
"es": {"aceptar", "rechazar"},
"fr": {"accepter", "refuser"},
"it": {"accettare", "rifiutare"},
"pt-BR": {"aceitar", "rejeitar"},
"nl": {"accepteren", "weigeren"},
"ro": {"accepte", "respingă"},
"cs": {"přijmout", "odmítnout"},
"id": {"menerima", "menolak"},
"tr": {"kabul etmeli", "reddetmeli"},
"vi": {"chấp nhận", "từ chối"},
"ru": {"принять", "отклонить"},
"uk": {"прийняти", "відхилити"},
"zh-Hans": {"接受", "拒绝"},
"zh-Hant": {"接受", "拒絕"},
"ja": {"受け入れる", "拒否する"},
"ko": {"수락", "거부"},
"ar": {"قبول", "رفض"},
"hi": {"मंज़ूर करना", "अस्वीकार करना"},
"th": {"ยอมรับ", "ปฏิเสธ"},
}
54 changes: 13 additions & 41 deletions internal/guard/site_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"testing"

"github.com/donislawdev/TestingFilesGenerator/internal/cli"
"github.com/donislawdev/TestingFilesGenerator/internal/damage"
"github.com/donislawdev/TestingFilesGenerator/internal/format"
_ "github.com/donislawdev/TestingFilesGenerator/internal/format/all"
"github.com/donislawdev/TestingFilesGenerator/internal/site"
Expand Down Expand Up @@ -121,6 +122,7 @@ func factsFromTheProgram(t *testing.T) site.Facts {
Version: version.Version,
Formats: formats,
ExitCodes: exitCodesInOrder(),
Damages: damagesTheProgramHas(),
Presets: presetFactsFromTheProgram(t),
Commands: commandsTheToolPrints(t),
Downloads: declaredDownloads(),
Expand All @@ -141,6 +143,17 @@ func factsFromTheProgram(t *testing.T) site.Facts {
}
}

// damagesTheProgramHas is every damage in the order tfg damage lists it, with
// the smallest file it takes at its defaults - the number that command prints,
// from the same call.
func damagesTheProgramHas() []site.Damage {
out := make([]site.Damage, 0, len(damage.All()))
for _, d := range damage.All() {
out = append(out, site.Damage{ID: d.ID, Smallest: d.Floor(d.Defaults()), Settings: d.ParameterNames()})
}
return out
}

// languagesOnDisk reads every language description under web/content.
func languagesOnDisk(t *testing.T) []site.Language {
t.Helper()
Expand Down Expand Up @@ -486,47 +499,6 @@ func TestEveryPageExistsInEveryLanguage(t *testing.T) {
}
}

// TestThePolishTextStaysOnThePolishPages holds the boundary the owner set when
// the site was allowed into this repository on 2026-08-26.
//
// D9 says text in the repository is English, and the criterion is the place
// rather than the reader. The site extends that rule to a second language and
// the extension is only as good as its border - so the border is machine
// checked here rather than remembered. Anything outside a pl directory that
// carries a character above ASCII is either a translation that leaked or an
// English page somebody typed a curly quote into, and both are defects.
func TestThePolishTextStaysOnThePolishPages(t *testing.T) {
root := webRoot(t)
text := map[string]bool{".html": true, ".json": true, ".css": true, ".xml": true, ".txt": true}
err := filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() || !text[strings.ToLower(filepath.Ext(p))] {
return err
}
rel, relErr := filepath.Rel(root, p)
if relErr != nil {
return relErr
}
slashed := filepath.ToSlash(rel)
polish := strings.Contains(slashed, "/pl/") || strings.HasPrefix(slashed, "pl/")
b, readErr := os.ReadFile(p)
if readErr != nil {
return readErr
}
for n, line := range strings.Split(string(b), "\n") {
for col, r := range line {
if r > 127 && !polish {
t.Errorf("web/%s:%d:%d holds %q - only the Polish pages may carry it", slashed, n+1, col+1, r)
return nil
}
}
}
return nil
})
if err != nil {
t.Fatalf("walking the site: %v", err)
}
}

// TestTheSiteFollowsThePunctuationRule applies rule 13 to the site.
//
// Fenced samples are left alone, the same way the README guard leaves licence
Expand Down
Loading
Loading