feat(i18n): add locale dictionaries and pilot surfaces
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import { expect, type Page, test } from "@playwright/test"
|
||||
|
||||
async function signInAsAdmin(page: Page) {
|
||||
async function setEnglishLocaleCookie(page: Page, baseURL?: string) {
|
||||
await page.context().addCookies([
|
||||
{
|
||||
name: "stock-manager-locale",
|
||||
value: "en",
|
||||
url: baseURL ?? "http://127.0.0.1:3100",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
async function signInAsAdmin(page: Page, baseURL?: string) {
|
||||
await setEnglishLocaleCookie(page, baseURL)
|
||||
await page.goto("/login")
|
||||
await page.getByLabel("Username").fill("admin")
|
||||
await page.getByLabel("Password").fill("admin-password")
|
||||
@@ -9,7 +20,12 @@ async function signInAsAdmin(page: Page) {
|
||||
}
|
||||
|
||||
test.describe("main app smoke", () => {
|
||||
test("redirects unauthenticated users to login", async ({ page }) => {
|
||||
test("redirects unauthenticated users to login", async ({
|
||||
baseURL,
|
||||
page,
|
||||
}) => {
|
||||
await setEnglishLocaleCookie(page, baseURL)
|
||||
|
||||
await page.goto("/admin/users")
|
||||
|
||||
await expect(page).toHaveURL(/\/login/)
|
||||
@@ -17,8 +33,11 @@ test.describe("main app smoke", () => {
|
||||
await expect(page.getByRole("button", { name: "Sign In" })).toBeVisible()
|
||||
})
|
||||
|
||||
test("signs in as seeded admin and opens the dashboard", async ({ page }) => {
|
||||
await signInAsAdmin(page)
|
||||
test("signs in as seeded admin and opens the dashboard", async ({
|
||||
baseURL,
|
||||
page,
|
||||
}) => {
|
||||
await signInAsAdmin(page, baseURL)
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible()
|
||||
await expect(page.getByText("E2E Admin")).toBeVisible()
|
||||
@@ -27,8 +46,11 @@ test.describe("main app smoke", () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test("admin can open users and inventory pages", async ({ page }) => {
|
||||
await signInAsAdmin(page)
|
||||
test("admin can open users and inventory pages", async ({
|
||||
baseURL,
|
||||
page,
|
||||
}) => {
|
||||
await signInAsAdmin(page, baseURL)
|
||||
|
||||
await page.getByRole("link", { name: "Users" }).click()
|
||||
await expect(page).toHaveURL(/\/admin\/users/)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { expect, type Page, test } from "@playwright/test"
|
||||
|
||||
async function setSpanishLocaleCookie(page: Page, baseURL?: string) {
|
||||
await page.context().addCookies([
|
||||
{
|
||||
name: "stock-manager-locale",
|
||||
value: "es",
|
||||
url: baseURL ?? "http://127.0.0.1:3100",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
test.describe("i18n locale cookie", () => {
|
||||
test("renders Spanish pilot copy on login and dashboard home", async ({
|
||||
baseURL,
|
||||
page,
|
||||
}) => {
|
||||
await setSpanishLocaleCookie(page, baseURL)
|
||||
|
||||
await page.goto("/login")
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Iniciar sesión" }),
|
||||
).toBeVisible()
|
||||
await page.getByLabel("Usuario").fill("admin")
|
||||
await page.getByLabel("Contraseña").fill("admin-password")
|
||||
await page.getByRole("button", { name: "Iniciar sesión" }).click()
|
||||
|
||||
await expect(page).toHaveURL("/")
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Panel de control" }),
|
||||
).toBeVisible()
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "es")
|
||||
await expect(page.getByText("Total de artículos")).toBeVisible()
|
||||
await expect(page.getByText("Total de activos")).toBeVisible()
|
||||
await expect(page.getByText("Total de destinatarios")).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { dictionaries, getDictionary } from "@/i18n/dictionaries"
|
||||
import { SUPPORTED_LOCALES } from "@/i18n/locales"
|
||||
|
||||
describe("i18n dictionaries", () => {
|
||||
it("provides dictionaries for every supported locale and no extra locales", () => {
|
||||
expect(Object.keys(dictionaries).sort()).toEqual(
|
||||
[...SUPPORTED_LOCALES].sort(),
|
||||
)
|
||||
})
|
||||
|
||||
it("returns localized login copy for English and Spanish", () => {
|
||||
expect(getDictionary("en").login).toEqual({
|
||||
title: "Sign In",
|
||||
usernameLabel: "Username",
|
||||
passwordLabel: "Password",
|
||||
submitLabel: "Sign In",
|
||||
})
|
||||
|
||||
expect(getDictionary("es").login).toEqual({
|
||||
title: "Iniciar sesión",
|
||||
usernameLabel: "Usuario",
|
||||
passwordLabel: "Contraseña",
|
||||
submitLabel: "Iniciar sesión",
|
||||
})
|
||||
})
|
||||
|
||||
it("keeps dashboard home dictionary keys aligned across locales", () => {
|
||||
expect(getDictionary("en").dashboardHome).toEqual({
|
||||
heading: "Dashboard",
|
||||
cards: {
|
||||
items: {
|
||||
title: "Total Items",
|
||||
countLabel: "Total",
|
||||
},
|
||||
assets: {
|
||||
title: "Total Assets",
|
||||
countLabel: "Total",
|
||||
},
|
||||
recipients: {
|
||||
title: "Total Recipients",
|
||||
countLabel: "Total",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(getDictionary("es").dashboardHome).toEqual({
|
||||
heading: "Panel de control",
|
||||
cards: {
|
||||
items: {
|
||||
title: "Total de artículos",
|
||||
countLabel: "Total",
|
||||
},
|
||||
assets: {
|
||||
title: "Total de activos",
|
||||
countLabel: "Total",
|
||||
},
|
||||
recipients: {
|
||||
title: "Total de destinatarios",
|
||||
countLabel: "Total",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("has exact structural parity between English and Spanish dictionaries", () => {
|
||||
expect(extractKeyPaths(getDictionary("es"))).toEqual(
|
||||
extractKeyPaths(getDictionary("en")),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
function extractKeyPaths(value: unknown, prefix = ""): string[] {
|
||||
if (!isPlainObject(value)) return [prefix]
|
||||
|
||||
return Object.keys(value)
|
||||
.sort()
|
||||
.flatMap((key) =>
|
||||
extractKeyPaths(value[key], prefix ? `${prefix}.${key}` : key),
|
||||
)
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import {
|
||||
DEFAULT_LOCALE_ENV_VAR,
|
||||
FALLBACK_LOCALE,
|
||||
isLocale,
|
||||
LOCALE_COOKIE_NAME,
|
||||
resolveDefaultLocale,
|
||||
resolveLocale,
|
||||
SUPPORTED_LOCALES,
|
||||
} from "@/i18n/locales"
|
||||
|
||||
describe("i18n locales", () => {
|
||||
it("defines exactly English and Spanish with an env-configured default", () => {
|
||||
expect(SUPPORTED_LOCALES).toEqual(["en", "es"])
|
||||
expect(FALLBACK_LOCALE).toBe("en")
|
||||
expect(DEFAULT_LOCALE_ENV_VAR).toBe("STOCK_MANAGER_DEFAULT_LOCALE")
|
||||
expect(LOCALE_COOKIE_NAME).toBe("stock-manager-locale")
|
||||
})
|
||||
|
||||
it("accepts only exact supported locale codes", () => {
|
||||
expect(isLocale("en")).toBe(true)
|
||||
expect(isLocale("es")).toBe(true)
|
||||
|
||||
for (const value of ["", "ES", "es-MX", "fr", undefined, null, 1]) {
|
||||
expect(isLocale(value)).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it("resolves valid locale cookie values and falls back to the configured default for invalid values", () => {
|
||||
expect(resolveLocale("es", "en")).toBe("es")
|
||||
expect(resolveLocale("en", "es")).toBe("en")
|
||||
|
||||
for (const value of ["", "ES", "es-MX", "fr", undefined, null, 1]) {
|
||||
expect(resolveLocale(value, "es")).toBe("es")
|
||||
expect(resolveLocale(value, "en")).toBe("en")
|
||||
}
|
||||
})
|
||||
|
||||
it("resolves the configured default locale from env-like values with a safe English fallback", () => {
|
||||
expect(resolveDefaultLocale("es")).toBe("es")
|
||||
expect(resolveDefaultLocale("en")).toBe("en")
|
||||
|
||||
for (const value of ["", "ES", "es-MX", "fr", undefined, null, 1]) {
|
||||
expect(resolveDefaultLocale(value)).toBe("en")
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user