161 lines
5.6 KiB
Markdown
161 lines
5.6 KiB
Markdown
# Project Rules
|
|
|
|
## Language
|
|
|
|
- All code comments must be written in **English**.
|
|
- All git commit messages must be written in **English**.
|
|
|
|
## What NOT to commit
|
|
|
|
- The `examples/` directory — it contains sample images and fixtures used for manual
|
|
testing only. It is listed in `.gitignore` and must never be staged or committed.
|
|
|
|
## Localisation
|
|
|
|
**Rule:** Every table that stores human-readable text must have a companion `{table}_translations`
|
|
table. The base table always contains the English canonical text (used as fallback).
|
|
Translations live only in `_translations` tables — never as extra columns (`name_ru`, `title_de`).
|
|
|
|
### SQL pattern
|
|
|
|
```sql
|
|
-- Base table — English canonical text always present
|
|
CREATE TABLE cuisines (
|
|
slug VARCHAR(50) PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
sort_order SMALLINT NOT NULL DEFAULT 0
|
|
);
|
|
-- Translation companion
|
|
CREATE TABLE cuisine_translations (
|
|
cuisine_slug VARCHAR(50) REFERENCES cuisines(slug) ON DELETE CASCADE,
|
|
lang VARCHAR(10) NOT NULL,
|
|
name TEXT NOT NULL,
|
|
PRIMARY KEY (cuisine_slug, lang)
|
|
);
|
|
```
|
|
|
|
### Query pattern
|
|
|
|
```sql
|
|
SELECT COALESCE(ct.name, c.name) AS name
|
|
FROM cuisines c
|
|
LEFT JOIN cuisine_translations ct ON ct.cuisine_slug = c.slug AND ct.lang = $lang
|
|
```
|
|
|
|
### Already compliant tables
|
|
|
|
`units` + `unit_translations`, `ingredient_categories` + `ingredient_category_translations`,
|
|
`ingredients` + `ingredient_translations` + `ingredient_aliases`,
|
|
`recipes` + `recipe_translations`, `cuisines` + `cuisine_translations`,
|
|
`tags` + `tag_translations`, `dish_categories` + `dish_category_translations`,
|
|
`dishes` + `dish_translations`, `recipe_ingredients` + `recipe_ingredient_translations`,
|
|
`recipe_steps` + `recipe_step_translations`.
|
|
|
|
### Rule when adding new entities
|
|
|
|
When adding a new entity with text fields, always create a `{table}_translations`
|
|
companion table and use LEFT JOIN COALESCE in all read queries.
|
|
|
|
## File and Directory Naming
|
|
|
|
- **File names:** snake_case — `token_verifier.go`, `wire_gen.go`, `entity.go`.
|
|
- **Package directories:** lowercase, no separators — follow Go convention
|
|
(`savedrecipe/`, `openai/`, `infra/`). Do not use underscores in package names.
|
|
- **Non-package directories** (migrations, docs, scripts): snake_case is acceptable.
|
|
|
|
## Naming
|
|
|
|
**No abbreviated variable names.** Variables must use full descriptive names.
|
|
Single- or two-letter names are only allowed when the abbreviation *is* the full description
|
|
(e.g., a loop index `i`, a matrix coordinate `x`).
|
|
|
|
### Examples
|
|
|
|
| Avoid | Use instead |
|
|
|-------|-------------|
|
|
| `err` | `parseError`, `writeError`, `dbError`, `validationError` |
|
|
| `ctx` | `requestContext`, `handlerContext`, `queryContext` |
|
|
| `s` | `service`, `server` |
|
|
| `r` | `request`, `repository` |
|
|
| `w` | `writer`, `responseWriter` |
|
|
| `h` | `handler` |
|
|
| `req` | `request` |
|
|
| `res` | `response`, `result` |
|
|
| `msg` | `message` |
|
|
| `cfg` | `config` |
|
|
| `db` | `database` |
|
|
| `tx` | `transaction` |
|
|
| `buf` | `buffer` |
|
|
| `tmp` | `temp`, or a descriptive name |
|
|
|
|
This rule applies to all languages in this repo (Go and Dart).
|
|
In Go, name variables to avoid shadowing the `error` built-in and the `context` package —
|
|
use descriptive prefixes: `parseError`, `requestContext`, etc.
|
|
|
|
## Backend Documentation
|
|
|
|
**Rule:** `backend/README.md` must stay in sync with the code. When making backend changes,
|
|
update the README if the change affects any of the following:
|
|
|
|
- **Stack or dependencies** — new or removed libraries
|
|
- **Environment variables** — new, renamed, or removed variables
|
|
- **API endpoints** — new routes, changed paths or methods, removed endpoints
|
|
- **Makefile targets** — new or removed `make` commands
|
|
- **Project structure** — new top-level packages under `cmd/`, `internal/domain/`,
|
|
`internal/adapters/`, or `internal/infra/`
|
|
- **Database schema** — new tables added to the high-level table list
|
|
- **Import tools** — changes to `cmd/importoff` flags or behaviour
|
|
|
|
The README does **not** need updating for: internal refactoring that doesn't change the
|
|
public interface, bug fixes, test changes, or minor logic tweaks inside existing packages.
|
|
|
|
## Flutter Client Localisation
|
|
|
|
**Rule:** Every UI string in `client/` must go through `AppLocalizations`.
|
|
Never hardcode user-visible text in Dart source files.
|
|
|
|
### Current setup
|
|
|
|
- `flutter_localizations` (sdk: flutter) + `intl: ^0.20.2` in `client/pubspec.yaml`
|
|
- `generate: true` under `flutter:` in `client/pubspec.yaml`
|
|
- `client/l10n.yaml` — generator config (arb-dir, template-arb-file, output-class)
|
|
- Generated class: `package:food_ai/l10n/app_localizations.dart`
|
|
|
|
### Supported languages
|
|
|
|
`en`, `ru`, `es`, `de`, `fr`, `it`, `pt`, `zh`, `ja`, `ko`, `ar`, `hi`
|
|
|
|
### ARB files
|
|
|
|
All translation files live in `client/lib/l10n/`:
|
|
|
|
- `app_en.arb` — English (template / canonical)
|
|
- `app_ru.arb`, `app_es.arb`, `app_de.arb`, `app_fr.arb`, `app_it.arb`
|
|
- `app_pt.arb`, `app_zh.arb`, `app_ja.arb`, `app_ko.arb`, `app_ar.arb`, `app_hi.arb`
|
|
|
|
### Usage pattern
|
|
|
|
```dart
|
|
import 'package:food_ai/l10n/app_localizations.dart';
|
|
|
|
// Inside build():
|
|
final l10n = AppLocalizations.of(context)!;
|
|
Text(l10n.someKey)
|
|
```
|
|
|
|
### Adding new strings
|
|
|
|
1. Add the key + English value to `client/lib/l10n/app_en.arb` (template file).
|
|
2. Add the same key with the correct translation to **all other 11 ARB files**.
|
|
3. Run `flutter gen-l10n` inside `client/` to regenerate `AppLocalizations`.
|
|
4. Use `l10n.<newKey>` in the widget.
|
|
|
|
For parameterised strings use the ICU placeholder syntax in ARB files:
|
|
|
|
```json
|
|
"queuePosition": "Position {position}",
|
|
"@queuePosition": { "placeholders": { "position": { "type": "int" } } }
|
|
```
|
|
|
|
Then call `l10n.queuePosition(n)` in Dart.
|