Stage M1
This commit is contained in:
+73
-12
@@ -240,7 +240,51 @@ consumer transient too, which for `PrismaService` would mean a second connection
|
||||
|
||||
---
|
||||
|
||||
## 9. Naming conventions
|
||||
## 9. Internationalisation
|
||||
|
||||
Two languages, **two mechanisms**, split by who owns the string. Conflating them is the usual
|
||||
way an i18n project ends up half-finished. See
|
||||
[ADR-0013](./adr/0013-content-translations-in-typed-tables-ui-strings-in-message-catalogs.md).
|
||||
|
||||
| String | Owner | Lives in | Changes via |
|
||||
| -------------------------------------- | ----------------- | ----------------------------------- | ---------------- |
|
||||
| "Add to bag", "Filters" | Designers/devs | `src/messages/{vi,en}.json` | Deploy |
|
||||
| Sport names, sort labels, availability | Devs (code enums) | Message catalogs | Deploy |
|
||||
| Product name, description, spec rows | Merchandisers | `*_translations` tables | Admin, no deploy |
|
||||
| Category / collection / brand names | Merchandisers | `*_translations` tables | Admin, no deploy |
|
||||
| Colour labels ("Đen" / "Black") | Merchandisers | `product_option_value_translations` | Admin, no deploy |
|
||||
|
||||
### Resolution rules
|
||||
|
||||
1. **Locale in, resolved out.** The API takes `?locale=` (falling back to `Accept-Language`,
|
||||
then `vi`) and returns already-resolved strings. The frontend never sees a translation table
|
||||
and never writes fallback logic.
|
||||
2. **Field-level fallback.** A translation row wins per column; any column that is null or blank
|
||||
falls back to the base row. A product with a translated name but no translated description
|
||||
renders the translated name and the original description — never a blank.
|
||||
3. **Missing translations are legitimate.** Sizes (`S`, `M`, `L`) carry no translation rows at
|
||||
all, because they are identical in both languages. The fallback covers it.
|
||||
4. **Every cache key is namespaced by locale.** Forgetting this serves the first visitor's
|
||||
language to everyone — the classic i18n caching bug. `CACHE_KEYS` builds every key in one file
|
||||
precisely so this cannot be forgotten locally.
|
||||
|
||||
### URLs
|
||||
|
||||
Storefront uses `localePrefix: 'as-needed'`: Vietnamese from clean paths, English under `/en`.
|
||||
Product slugs are themselves translated, so `/products/ao-chay-bo-aero` and
|
||||
`/en/products/aero-run-tee` are the same product. Consequently:
|
||||
|
||||
- Every product carries `alternateSlugs`, which feeds `hreflang` alternates and the language
|
||||
switcher.
|
||||
- Product lookup matches a slug in _any_ locale, then redirects to the canonical URL for the
|
||||
requested locale. One redirect keeps every cross-locale link alive without duplicate content.
|
||||
|
||||
The admin resolves locale from a cookie with no URL segment — it is `noindex` everywhere, so
|
||||
locale-in-path would add a proxy hop and double the route tree for nothing.
|
||||
|
||||
---
|
||||
|
||||
## 10. Naming conventions
|
||||
|
||||
| Thing | Convention | Example |
|
||||
| --------------------- | ------------------------- | ------------------------------ |
|
||||
@@ -265,7 +309,7 @@ Booleans read as assertions: `isActive`, `hasVariants`, `canRefund`. Money field
|
||||
|
||||
---
|
||||
|
||||
## 10. Configuration and environment
|
||||
## 11. Configuration and environment
|
||||
|
||||
Four `.env` files, each with a committed `.env.example`:
|
||||
|
||||
@@ -292,7 +336,7 @@ Rules:
|
||||
|
||||
---
|
||||
|
||||
## 11. Where premature abstraction must be avoided
|
||||
## 12. Where premature abstraction must be avoided
|
||||
|
||||
Places where the instinct to generalise should be resisted until a second real case appears:
|
||||
|
||||
@@ -314,7 +358,7 @@ Places where the instinct to generalise should be resisted until a second real c
|
||||
|
||||
---
|
||||
|
||||
## 12. Architectural risks to prevent from day one
|
||||
## 13. Architectural risks to prevent from day one
|
||||
|
||||
| Risk | Why it is fatal later | Prevention in place |
|
||||
| ---------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
@@ -333,14 +377,31 @@ Places where the instinct to generalise should be resisted until a second real c
|
||||
|
||||
---
|
||||
|
||||
## 13. Deliberate limitations of milestone 0
|
||||
## 14. Deliberate limitations
|
||||
|
||||
Stated plainly so they are choices rather than oversights:
|
||||
Stated plainly so they are choices rather than oversights.
|
||||
|
||||
- **No token issuance.** Guards verify and enforce; login, refresh and registration are M2.
|
||||
- **No cart/order/payment tables.** The first migration stays reviewable; they arrive in M5.
|
||||
**Shipped (M0–M1)**
|
||||
|
||||
- Catalog reads: products, variants, options, categories, collections, brands, navigation —
|
||||
localised, cached, filtered and faceted.
|
||||
- RBAC enforcement, response envelope, structured logging, media pipeline.
|
||||
- Storefront browsing and PDP in Vietnamese and English.
|
||||
|
||||
**Not built yet, and why**
|
||||
|
||||
- **No token issuance.** Guards verify and enforce; login, refresh rotation and registration are
|
||||
M2. Every endpoint written from here is protected by default, before a credential exists.
|
||||
- **No cart, order or payment tables.** They arrive in M5, so the migration history stays
|
||||
reviewable and the variant model gets proven against real reads first.
|
||||
- **`best_selling` and `relevance` sorts fall back to newest.** There is no order data (M5) and
|
||||
no ranking (M6). Falling back is honest; a fake ranking would not be.
|
||||
- **No sport/gender facet counts.** Those dimensions are navigated by route, not refined within a
|
||||
page, so a count would render nowhere.
|
||||
- **Facet counts are computed per request.** Fine at this catalog size; the fix when it stops
|
||||
being fine is a search index (ADR-0012), not a bigger query.
|
||||
- **The event bus is in-process and lossy.** Anything that must not be lost stays in the same
|
||||
database transaction as its cause. A durable outbox comes when a use case demands it.
|
||||
- **No observability beyond logs.** OpenTelemetry traces and metrics are worth adding once there
|
||||
is production traffic to explain.
|
||||
- **No CDN, TLS or WAF config.** That belongs to the deployment repository, not this one.
|
||||
database transaction as its cause.
|
||||
- **No observability beyond logs.** Traces and metrics are worth adding once there is production
|
||||
traffic to explain.
|
||||
- **No CDN, TLS or WAF config.** That belongs to the deployment repository.
|
||||
|
||||
Reference in New Issue
Block a user