Use one verb per action in the English UI: sign in, sign out, create account #49
@@ -276,10 +276,12 @@ Same test before adding a row to a table or the file map — a clause, not a par
|
||||
A pure view-model builder takes an optional `t` defaulting to its own English, so a unit test reads
|
||||
in words; handlers pass `ctx.t`.
|
||||
- **One verb per action in the English UI: sign in, sign out, create account.** Not "log in",
|
||||
"log out" or "sign up" — a second spelling for one button reads as a second thing. Guarded by
|
||||
`src/i18n/load.test.ts` (a test file cannot live in `locales/`, which loads every `.ts` in it as a
|
||||
catalog); the noun ("a sign-in error") is unaffected. A plugin's catalog
|
||||
and every other locale follow the same one-verb-per-action rule in their own language.
|
||||
"log out" or "sign up", inflections included — a second spelling for one button reads as a second
|
||||
thing; the noun ("a sign-in error", "the sign-in identifier") is unaffected. Guarded over the
|
||||
shipped core catalog by `src/i18n/load.test.ts` (a test file cannot live in `locales/`, which loads
|
||||
every `.ts` in it as a catalog); a plugin's catalog and every other locale follow the same rule in
|
||||
their own language, unguarded. An unmapped Kratos id still renders Kratos' own wording — map it to
|
||||
hold the line.
|
||||
- Use well formed, standard compliant, rich URIs. Prefer state in the URL over POST:ing in for
|
||||
for example list pages with filters and pagination. Do: "ids=x&ids=y" and not "ids[]=x&ids[]=y"
|
||||
and not "ids=x,y".
|
||||
|
||||
@@ -129,7 +129,7 @@ const messages = {
|
||||
"admin.users.error.selfDeactivate": "You can't deactivate your own account.",
|
||||
"admin.users.error.selfDelete": "You can't delete your own account.",
|
||||
"admin.users.field.email": "Email",
|
||||
"admin.users.field.emailHint": "The login identifier — can't be changed here.",
|
||||
"admin.users.field.emailHint": "The sign-in identifier — can't be changed here.",
|
||||
"admin.users.field.first": "First name",
|
||||
"admin.users.field.last": "Last name",
|
||||
"admin.users.field.password": "Password",
|
||||
|
||||
@@ -18,17 +18,16 @@ async function fixture(files: Record<string, string>): Promise<{ localesDir: str
|
||||
return { localesDir: join(root, "locales"), pluginsDir: join(root, "plugins") };
|
||||
}
|
||||
|
||||
test("the shipped core catalogs load and agree key for key", async () => {
|
||||
test("the shipped core catalogs load, agree key for key, and use one verb per action", async () => {
|
||||
const loaded = await loadI18n(); // no args ⇒ the real src/i18n/locales + plugins/
|
||||
assert.ok(loaded.available.includes("en-US"));
|
||||
assert.ok(loaded.available.includes("sv-SE"));
|
||||
assert.deepEqual([...loaded.available].sort(), loaded.available); // sorted, so "sv" resolves deterministically
|
||||
assert.ok(Object.keys(loaded.core.get("en-US") ?? {}).length > 20);
|
||||
|
||||
// One verb per action: sign in / sign out / create account. Two spellings for one button ("Log in"
|
||||
// on the landing, "Sign in" in the sidebar) read as two different things. Nouns ("a sign-in error")
|
||||
// are fine — only the competing verbs are out. AGENTS.md → Rules.
|
||||
const competing = /\b(log[\s-]?in|log[\s-]?out|sign[\s-]?up)\b/i;
|
||||
// One verb per action; inflected too, and the noun ("a sign-in error") is fine. AGENTS.md → Rules.
|
||||
// The lookbehind spares a path (/login) and a word ending in one (blog in…).
|
||||
const competing = /(?<![/\w])(log(?:ged|ging)?[\s-]?(?:in|out)|sign(?:ed|ing)?[\s-]?up)s?\b/i;
|
||||
const offenders = Object.entries(loaded.core.get("en-US") ?? {})
|
||||
.map(([key, message]) => [key, typeof message === "string" ? message : Object.values(message).join(" ")] as const)
|
||||
.filter(([, text]) => competing.test(text))
|
||||
|
||||
Reference in New Issue
Block a user