[UPD] commit rule: English subject first, French title under --- FR ---
git log --oneline shows only the subject, and it read in whichever language the author was thinking in. The subject and the body under it are now in English, then --- FR --- opens the French section, which starts with the subject translated under the same tag. The hook refuses a --- EN --- marker and a French section without that title, and checks the title like a subject; it does not count against the body budget. Whether the subject is really English is not checked. /commit and /git_prepare_merge follow. Checked: 55 hook tests, 7 of them new; i18n tests pass. --- FR --- [UPD] règle de commit : sujet anglais d'abord, titre FR sous --- FR --- git log --oneline ne montre que le sujet, et il se lisait dans la langue où l'auteur pensait. Le sujet et le corps qui le suit sont désormais en anglais, puis --- FR --- ouvre la section française, qui commence par le sujet traduit sous le même tag. Le hook refuse un marqueur --- EN --- et une section française sans ce titre, et juge ce titre comme un sujet ; il ne compte pas dans le budget du corps. Que le sujet soit vraiment en anglais ne se vérifie pas. /commit et /git_prepare_merge suivent. Vérifié : 55 tests du hook, dont 7 nouveaux ; tests i18n au vert. Assisted-by: Claude Opus 5.5
This commit is contained in:
parent
aa3f99e746
commit
76c548a781
6 changed files with 186 additions and 40 deletions
|
|
@ -90,9 +90,9 @@ liste, et elle est vraie demain.
|
||||||
- Branches : `develop` (développement), `master` (production)
|
- Branches : `develop` (développement), `master` (production)
|
||||||
- Pas de submodules Git — utilise **Google Repo** pour les addons
|
- Pas de submodules Git — utilise **Google Repo** pour les addons
|
||||||
- Manifests XML dans `manifest/` pour chaque version Odoo
|
- Manifests XML dans `manifest/` pour chaque version Odoo
|
||||||
- Format de commit : `[TYPE] portée : sujet`, sujet à l'impératif, 72
|
- Format de commit : `[TYPE] scope: subject`, sujet **en anglais**, à
|
||||||
caractères au plus. Tags réellement utilisés : `[UPD]`, `[FIX]`, `[ADD]`,
|
l'impératif, 72 caractères au plus. Tags réellement utilisés : `[UPD]`,
|
||||||
`[IMP]`, `[REF]`.
|
`[FIX]`, `[ADD]`, `[IMP]`, `[REF]`.
|
||||||
- **Nommer les fichiers à l'indexation, jamais `git add -A`** : `private/` et
|
- **Nommer les fichiers à l'indexation, jamais `git add -A`** : `private/` et
|
||||||
`tasks/` ne sont pas suivis EXPRÈS, et un ratissage les commit. Il emporte
|
`tasks/` ne sont pas suivis EXPRÈS, et un ratissage les commit. Il emporte
|
||||||
aussi ce qui est en cours ailleurs dans le checkout, sous un sujet qui ne le
|
aussi ce qui est en cours ailleurs dans le checkout, sous un sujet qui ne le
|
||||||
|
|
@ -117,14 +117,17 @@ Le sujet résume le commit ENTIER, pas sa plus grosse pièce. S'il lui faut un
|
||||||
|
|
||||||
Si le travail n'entre décidément pas dans une phrase de 72 caractères, ne pas
|
Si le travail n'entre décidément pas dans une phrase de 72 caractères, ne pas
|
||||||
en écrire une amputée : des **mots-clés qui résument**, séparés par des
|
en écrire une amputée : des **mots-clés qui résument**, séparés par des
|
||||||
virgules, en disent plus dans la même place — `[FIX] proxmox : pmxcfs à terre,
|
virgules, en disent plus dans la même place — `[FIX] proxmox: pmxcfs down,
|
||||||
pvesm muet, diagnostic à la source`. C'est un repli, pas un défaut : la phrase
|
pvesm silent, diagnosis at the source`. C'est un repli, pas un défaut : la
|
||||||
reste préférable quand elle tient.
|
phrase reste préférable quand elle tient.
|
||||||
|
|
||||||
Un garde-fou refuse le mécanique. Sur le sujet : tag absent, plus de 72
|
Un garde-fou refuse le mécanique. Sur le sujet : tag absent, plus de 72
|
||||||
caractères, ouverture sur une citation. Sur le corps : plus de 10 lignes pour
|
caractères, ouverture sur une citation. Sur le corps : plus de 10 lignes pour
|
||||||
une langue, une adresse IP, un courriel, un chemin de compte. Ce qui reste un
|
une langue, une adresse IP, un courriel, un chemin de compte. Sur l'ordre des
|
||||||
jugement — « ce corps raconte-t-il l'enquête » — n'est vérifié par personne.
|
langues : un marqueur `--- EN ---`, une section française qui ne s'ouvre pas
|
||||||
|
sur le sujet traduit sous le même tag. Que le sujet soit bien en anglais
|
||||||
|
reste à l'auteur. Ce qui reste un jugement — « ce corps raconte-t-il
|
||||||
|
l'enquête » — n'est vérifié par personne.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git config core.hooksPath script/git/hooks # une fois par clone
|
git config core.hooksPath script/git/hooks # une fois par clone
|
||||||
|
|
@ -141,8 +144,9 @@ Trois exigences, sans exception — `AI_POLICY.md` en donne la raison :
|
||||||
- Trailer `Assisted-by: <modèle>`, une ligne par modèle. C'est **binaire** :
|
- Trailer `Assisted-by: <modèle>`, une ligne par modèle. C'est **binaire** :
|
||||||
il y a eu IA ou non, aucun seuil à apprécier.
|
il y a eu IA ou non, aucun seuil à apprécier.
|
||||||
- **Jamais** d'IA dans `Co-authored-by:` — ce champ est réservé aux humains.
|
- **Jamais** d'IA dans `Co-authored-by:` — ce champ est réservé aux humains.
|
||||||
- Corps **bilingue** : le corps, puis `--- FR ---` (ou `--- EN ---`, le
|
- Message **bilingue, l'anglais d'abord** : le sujet et le corps en anglais,
|
||||||
marqueur nomme la langue de ce qui SUIT), puis la traduction.
|
puis `--- FR ---`, puis le sujet traduit en français sous le même tag, puis
|
||||||
|
le corps traduit. `--- EN ---` n'existe plus : l'ordre ne varie jamais.
|
||||||
|
|
||||||
Court et direct : **8 lignes par langue**, 10 est un plafond. Le corps dit
|
Court et direct : **8 lignes par langue**, 10 est un plafond. Le corps dit
|
||||||
pourquoi c'était nécessaire, puis s'arrête. Rien de ce que le diff montre
|
pourquoi c'était nécessaire, puis s'arrête. Rien de ce que le diff montre
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ The first five cover every one of the last 400 commits. Reach for `[REM]`,
|
||||||
### Format
|
### Format
|
||||||
|
|
||||||
```
|
```
|
||||||
[TAG] scope: short description in imperative mood
|
[TAG] scope: short description in imperative mood, in English
|
||||||
|
|
||||||
Explain WHY the change was made — the diff already shows what. Name the
|
Explain WHY the change was made — the diff already shows what. Name the
|
||||||
failure mode, and what was measured rather than assumed, in words a
|
failure mode, and what was measured rather than assumed, in words a
|
||||||
|
|
@ -87,11 +87,18 @@ Wrap at 80 characters.
|
||||||
|
|
||||||
--- FR ---
|
--- FR ---
|
||||||
|
|
||||||
|
[TAG] portée : le sujet, traduit en français
|
||||||
|
|
||||||
The same body, translated.
|
The same body, translated.
|
||||||
|
|
||||||
Assisted-by: {MODEL}
|
Assisted-by: {MODEL}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The subject line is in ENGLISH, and the English body follows it: together
|
||||||
|
they are the English section. `--- FR ---` then opens the French section,
|
||||||
|
which starts with the subject translated, under the same tag, and goes on
|
||||||
|
with the translated body.
|
||||||
|
|
||||||
### The subject line
|
### The subject line
|
||||||
|
|
||||||
The subject is read a hundred times for every time the body is read: in
|
The subject is read a hundred times for every time the body is read: in
|
||||||
|
|
@ -108,9 +115,9 @@ built on them reads well and tells the next reader nothing:
|
||||||
|
|
||||||
| Instead of | Write |
|
| Instead of | Write |
|
||||||
|-----------|-------|
|
|-----------|-------|
|
||||||
| `[FIX] nettoyage : les enfants s'en vont avec leur rebond` | `[FIX] nettoyage : les entrées ssh qui rebondissent par une VM effacée` |
|
| `[FIX] cleanup: the children leave with their bounce` | `[FIX] cleanup: ssh entries that bounce through a deleted VM` |
|
||||||
| `[FIX] proxmox : « il manque le stockage » était le symptôme, pas la cause` | `[FIX] proxmox : signaler pmxcfs à terre, et non « aucun stockage »` |
|
| `[FIX] proxmox: "storage is missing" was the symptom, not the cause` | `[FIX] proxmox: report pmxcfs down, not "no storage"` |
|
||||||
| `[FIX] migration: un module fautif n'emporte plus tout le lot` | `[FIX] migration: isoler l'échec d'un module dans la désinstallation` |
|
| `[FIX] migration: a faulty module no longer takes the whole batch` | `[FIX] migration: isolate a module's failure during uninstall` |
|
||||||
|
|
||||||
The scope is not the subject. `proxmox` says WHERE; the words after the colon
|
The scope is not the subject. `proxmox` says WHERE; the words after the colon
|
||||||
must say WHAT. A subject that works with its scope removed is usually the
|
must say WHAT. A subject that works with its scope removed is usually the
|
||||||
|
|
@ -130,8 +137,8 @@ one — write **keywords that summarise**. A comma-separated list of the nouns
|
||||||
that matter says more in the space than half a sentence does:
|
that matter says more in the space than half a sentence does:
|
||||||
|
|
||||||
```
|
```
|
||||||
[FIX] proxmox : pmxcfs à terre, pvesm muet, diagnostic à la source
|
[FIX] proxmox: pmxcfs down, pvesm silent, diagnosis at the source
|
||||||
[ADD] migration : copies de site, index doublés, réglages perdus
|
[ADD] migration: site copies, doubled indexes, lost settings
|
||||||
```
|
```
|
||||||
|
|
||||||
That form is a fallback, not a default. Prefer the sentence when it fits.
|
That form is a fallback, not a default. Prefer the sentence when it fits.
|
||||||
|
|
@ -139,7 +146,10 @@ That form is a fallback, not a default. Prefer the sentence when it fits.
|
||||||
**The guard rail.** `script/git/hooks/commit-msg` refuses a subject with no
|
**The guard rail.** `script/git/hooks/commit-msg` refuses a subject with no
|
||||||
tag, one over 72 characters, and one opening on a quotation. It reads the body
|
tag, one over 72 characters, and one opening on a quotation. It reads the body
|
||||||
too: over ten lines for one language, an IP address, an e-mail, and a
|
too: over ten lines for one language, an IP address, an e-mail, and a
|
||||||
`/home/<account>/` path. Install the hook with `git config core.hooksPath script/git/hooks`; `git commit
|
`/home/<account>/` path. And the order of the languages: a `--- EN ---`
|
||||||
|
marker, or a French section that does not open on the translated subject
|
||||||
|
under the same tag. Whether the subject is really in English stays yours to
|
||||||
|
check. Install the hook with `git config core.hooksPath script/git/hooks`; `git commit
|
||||||
--no-verify` passes a legitimate exception. It checks only what is mechanical
|
--no-verify` passes a legitimate exception. It checks only what is mechanical
|
||||||
— whether the subject says what the code is about, and whether the body tells
|
— whether the subject says what the code is about, and whether the body tells
|
||||||
the story instead of the mechanism, stay judgements, and the tests above are
|
the story instead of the mechanism, stay judgements, and the tests above are
|
||||||
|
|
@ -187,12 +197,15 @@ goes.
|
||||||
|
|
||||||
### Bilingual body
|
### Bilingual body
|
||||||
|
|
||||||
Every AI-assisted commit carries its body twice. Write it first in whichever
|
Every AI-assisted commit carries its subject and its body twice, English
|
||||||
language you were thinking in, then the marker, then the translation.
|
first: the subject and the body under it are in English, then `--- FR ---`,
|
||||||
|
then the subject translated into French under the same tag, then the body
|
||||||
|
translated. The order never varies — `--- EN ---` is refused — so a reader of
|
||||||
|
`git log --oneline` always gets English, and a French reader finds the French
|
||||||
|
title where the French section starts.
|
||||||
|
|
||||||
The marker names the language of what FOLLOWS it: `--- FR ---` after an
|
The translated subject obeys the same rules as the subject: 72 characters,
|
||||||
English body, `--- EN ---` after a French one. One marker per commit, never
|
no opening quotation. It does not count against the body's line budget.
|
||||||
both.
|
|
||||||
|
|
||||||
Translate, do not re-summarise: a reader of either language must get the same
|
Translate, do not re-summarise: a reader of either language must get the same
|
||||||
reasoning, the same measured figures and the same caveats.
|
reasoning, the same measured figures and the same caveats.
|
||||||
|
|
@ -259,12 +272,14 @@ the subject you just wrote, and no others. When one file carries two subjects,
|
||||||
git status --porcelain
|
git status --porcelain
|
||||||
git add script/module/thing.py test/test_thing.py
|
git add script/module/thing.py test/test_thing.py
|
||||||
git -c user.name="Your Name" -c user.email="your@email.com" commit -F - <<'MSG'
|
git -c user.name="Your Name" -c user.email="your@email.com" commit -F - <<'MSG'
|
||||||
[TAG] scope: description
|
[TAG] scope: description in English
|
||||||
|
|
||||||
Explain WHY here.
|
Explain WHY here.
|
||||||
|
|
||||||
--- FR ---
|
--- FR ---
|
||||||
|
|
||||||
|
[TAG] portée : la description en français
|
||||||
|
|
||||||
The same body, translated.
|
The same body, translated.
|
||||||
|
|
||||||
Assisted-by: {MODEL}
|
Assisted-by: {MODEL}
|
||||||
|
|
|
||||||
|
|
@ -117,12 +117,14 @@ The shape, as this repository writes it:
|
||||||
```
|
```
|
||||||
Merge branch '<branch>'
|
Merge branch '<branch>'
|
||||||
|
|
||||||
[TAG] scope: what the branch delivers, imperative, 72 characters maximum
|
[TAG] scope: what the branch delivers, in English, 72 characters maximum
|
||||||
|
|
||||||
<N> commits. Why the branch existed: the failure mode it removes, the
|
<N> commits. Why the branch existed: the failure mode it removes, the
|
||||||
figure that bounds it, what was verified and how. Wrap at 80 characters.
|
figure that bounds it, what was verified and how. Wrap at 80 characters.
|
||||||
|
|
||||||
--- EN ---
|
--- FR ---
|
||||||
|
|
||||||
|
[TAG] portée : ce que livre la branche, traduit en français
|
||||||
|
|
||||||
The same body, translated.
|
The same body, translated.
|
||||||
|
|
||||||
|
|
@ -143,14 +145,15 @@ commits the branch carries, then say what they add up to. No bullet list, no
|
||||||
per-commit rundown: `git log <base>..HEAD` already gives that, and a body
|
per-commit rundown: `git log <base>..HEAD` already gives that, and a body
|
||||||
repeating it teaches nothing.
|
repeating it teaches nothing.
|
||||||
|
|
||||||
The bilingual rule holds — body, then the marker naming the language of what
|
The bilingual rule holds as for a commit — the tagged line and the body in
|
||||||
FOLLOWS, then the translation — as does the ban on naming an AI in
|
English, then `--- FR ---`, then the tagged line translated under the same
|
||||||
|
tag, then the translated body — as does the ban on naming an AI in
|
||||||
`Co-authored-by:`.
|
`Co-authored-by:`.
|
||||||
|
|
||||||
**The hook does not check this one.** `script/git/hooks/commit-msg` skips any
|
**The hook does not check this one.** `script/git/hooks/commit-msg` skips any
|
||||||
message beginning with `Merge `, along with `Revert `, `fixup!` and `squash!`.
|
message beginning with `Merge `, along with `Revert `, `fixup!` and `squash!`.
|
||||||
Length, addresses and account paths pass unchallenged here, so the discipline
|
Length, addresses, account paths and the order of the languages pass
|
||||||
is entirely yours.
|
unchallenged here, so the discipline is entirely yours.
|
||||||
|
|
||||||
### 3. Hand it over
|
### 3. Hand it over
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ d'emploi dans `conf/template_claude_commands_commit.md`. Ce module en vérifie
|
||||||
la part MÉCANIQUE, sur deux plans :
|
la part MÉCANIQUE, sur deux plans :
|
||||||
|
|
||||||
- le sujet : le tag, la longueur, l'ouverture sur une citation ;
|
- le sujet : le tag, la longueur, l'ouverture sur une citation ;
|
||||||
- le corps : sa longueur par langue, et la donnée identifiante.
|
- le corps : sa longueur par langue, et la donnée identifiante ;
|
||||||
|
- l'ordre des langues : le sujet et le corps qui le suit sont en anglais,
|
||||||
|
« --- FR --- » ouvre la traduction, et celle-ci commence par le sujet
|
||||||
|
traduit, sous le même tag.
|
||||||
|
|
||||||
|
Que le sujet soit VRAIMENT en anglais ne se vérifie pas : seul l'ordre des
|
||||||
|
moitiés et la présence du titre traduit le sont.
|
||||||
|
|
||||||
Le reste — « ce sujet dit-il sur quoi porte le code », « ce corps raconte-t-il
|
Le reste — « ce sujet dit-il sur quoi porte le code », « ce corps raconte-t-il
|
||||||
l'enquête plutôt que le fonctionnement » — est un jugement, et aucun hook ne
|
l'enquête plutôt que le fonctionnement » — est un jugement, et aucun hook ne
|
||||||
|
|
@ -64,6 +70,8 @@ GENERATED = ("Merge ", "Revert ", "fixup!", "squash!", "amend!")
|
||||||
QUOTES = ("«", '"', "'", "`", "“", "‘")
|
QUOTES = ("«", '"', "'", "`", "“", "‘")
|
||||||
|
|
||||||
# Le marqueur nomme la langue de ce qui SUIT : il sépare les deux moitiés.
|
# Le marqueur nomme la langue de ce qui SUIT : il sépare les deux moitiés.
|
||||||
|
# Seul « --- FR --- » est admis, l'anglais venant toujours en premier ; « EN »
|
||||||
|
# est reconnu pour être refusé avec un message, et non ignoré.
|
||||||
MARKER = re.compile(r"^---\s*(FR|EN)\s*---\s*$", re.MULTILINE)
|
MARKER = re.compile(r"^---\s*(FR|EN)\s*---\s*$", re.MULTILINE)
|
||||||
|
|
||||||
# `git commit --cleanup=scissors` laisse le diff en clair sous cette ligne :
|
# `git commit --cleanup=scissors` laisse le diff en clair sous cette ligne :
|
||||||
|
|
@ -133,14 +141,26 @@ def _moities(body: str) -> list:
|
||||||
return [part for part in MARKER.split(body) if part not in ("FR", "EN")]
|
return [part for part in MARKER.split(body) if part not in ("FR", "EN")]
|
||||||
|
|
||||||
|
|
||||||
|
def _tag_of(subject: str):
|
||||||
|
"""Le tag qui ouvre le sujet, sans crochets ; None s'il n'y en a pas."""
|
||||||
|
for candidate in TAGS:
|
||||||
|
if subject.startswith(f"[{candidate}]"):
|
||||||
|
return candidate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _titre_traduit(moitie: str) -> str:
|
||||||
|
"""La première ligne non vide d'une moitié traduite : son titre."""
|
||||||
|
for ligne in moitie.split("\n"):
|
||||||
|
if ligne.strip():
|
||||||
|
return ligne.strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _check_subject(subject: str) -> list:
|
def _check_subject(subject: str) -> list:
|
||||||
problems = []
|
problems = []
|
||||||
|
|
||||||
tag = None
|
tag = _tag_of(subject)
|
||||||
for candidate in TAGS:
|
|
||||||
if subject.startswith(f"[{candidate}]"):
|
|
||||||
tag = candidate
|
|
||||||
break
|
|
||||||
if tag is None:
|
if tag is None:
|
||||||
problems.append(
|
problems.append(
|
||||||
t("the subject must start with a tag: %s")
|
t("the subject must start with a tag: %s")
|
||||||
|
|
@ -179,8 +199,12 @@ def _check_body(sans_trailers: str, avec_trailers: str) -> list:
|
||||||
"""
|
"""
|
||||||
problems = []
|
problems = []
|
||||||
|
|
||||||
for moitie in _moities(sans_trailers):
|
for rang, moitie in enumerate(_moities(sans_trailers)):
|
||||||
pleines = [ligne for ligne in moitie.split("\n") if ligne.strip()]
|
pleines = [ligne for ligne in moitie.split("\n") if ligne.strip()]
|
||||||
|
# Le titre traduit est le sujet de sa langue : il ne pèse pas sur le
|
||||||
|
# budget du corps, pas plus que le sujet anglais.
|
||||||
|
if rang and pleines and _tag_of(pleines[0].strip()):
|
||||||
|
pleines = pleines[1:]
|
||||||
if len(pleines) > MAX_BODY:
|
if len(pleines) > MAX_BODY:
|
||||||
problems.append(
|
problems.append(
|
||||||
t(
|
t(
|
||||||
|
|
@ -243,11 +267,45 @@ def _check_body(sans_trailers: str, avec_trailers: str) -> list:
|
||||||
return problems
|
return problems
|
||||||
|
|
||||||
|
|
||||||
|
def _check_langues(subject: str, sans_trailers: str) -> list:
|
||||||
|
"""L'anglais d'abord, puis « --- FR --- » et le sujet traduit.
|
||||||
|
|
||||||
|
Un message sans marqueur n'est pas bilingue et n'est pas jugé ici.
|
||||||
|
"""
|
||||||
|
marqueurs = MARKER.findall(sans_trailers)
|
||||||
|
if not marqueurs:
|
||||||
|
return []
|
||||||
|
if "EN" in marqueurs:
|
||||||
|
return [
|
||||||
|
t(
|
||||||
|
"the marker is « --- EN --- ». The subject and the body under it\n"
|
||||||
|
" are in English; « --- FR --- » opens the French translation."
|
||||||
|
)
|
||||||
|
]
|
||||||
|
tag = _tag_of(subject)
|
||||||
|
titre = _titre_traduit(_moities(sans_trailers)[-1])
|
||||||
|
if tag is None or not titre.startswith(f"[{tag}]"):
|
||||||
|
return [
|
||||||
|
t(
|
||||||
|
"the French section must open on the translated subject,\n"
|
||||||
|
" under the same tag: %s"
|
||||||
|
)
|
||||||
|
% ("[%s] …" % (tag or "TAG"))
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
t("French subject: %s") % probleme
|
||||||
|
for probleme in _check_subject(titre)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def check(message: str) -> list:
|
def check(message: str) -> list:
|
||||||
"""Rend la liste des problèmes. Vide si le message passe."""
|
"""Rend la liste des problèmes. Vide si le message passe."""
|
||||||
subject = subject_of(message)
|
subject = subject_of(message)
|
||||||
if not subject or subject.startswith(GENERATED):
|
if not subject or subject.startswith(GENERATED):
|
||||||
return []
|
return []
|
||||||
return _check_subject(subject) + _check_body(
|
sans_trailers = body_of(message)
|
||||||
body_of(message), body_of(message, trailers=True)
|
return (
|
||||||
|
_check_subject(subject)
|
||||||
|
+ _check_langues(subject, sans_trailers)
|
||||||
|
+ _check_body(sans_trailers, body_of(message, trailers=True))
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9446,6 +9446,24 @@ TRANSLATIONS = {
|
||||||
"en": "the subject opens on a quotation. A screen message is\n"
|
"en": "the subject opens on a quotation. A screen message is\n"
|
||||||
" evidence: it belongs in the body. The subject names the cause.",
|
" evidence: it belongs in the body. The subject names the cause.",
|
||||||
},
|
},
|
||||||
|
"the marker is « --- EN --- ». The subject and the body under it\n"
|
||||||
|
" are in English; « --- FR --- » opens the French translation.": {
|
||||||
|
"fr": "le marqueur est « --- EN --- ». Le sujet et le corps qui le suit\n"
|
||||||
|
" sont en anglais ; « --- FR --- » ouvre la traduction française.",
|
||||||
|
"en": "the marker is « --- EN --- ». The subject and the body under it\n"
|
||||||
|
" are in English; « --- FR --- » opens the French translation.",
|
||||||
|
},
|
||||||
|
"the French section must open on the translated subject,\n"
|
||||||
|
" under the same tag: %s": {
|
||||||
|
"fr": "la section française doit s'ouvrir sur le sujet traduit,\n"
|
||||||
|
" sous le même tag : %s",
|
||||||
|
"en": "the French section must open on the translated subject,\n"
|
||||||
|
" under the same tag: %s",
|
||||||
|
},
|
||||||
|
"French subject: %s": {
|
||||||
|
"fr": "sujet français : %s",
|
||||||
|
"en": "French subject: %s",
|
||||||
|
},
|
||||||
"the body is %s lines for one language, %s at most.\n"
|
"the body is %s lines for one language, %s at most.\n"
|
||||||
" The body says why it was necessary, then stops.\n"
|
" The body says why it was necessary, then stops.\n"
|
||||||
" The investigation, the dated measurements and the dead ends go\n"
|
" The investigation, the dated measurements and the dead ends go\n"
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,55 @@ class TestCeQuiEstRefuse(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def _message(corps):
|
def _message(corps):
|
||||||
return "[FIX] portée : quelque chose\n\n" + corps + "\n"
|
return "[FIX] scope: something\n\n" + corps + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
# Le sujet de _message, traduit : ce qui ouvre la section française.
|
||||||
|
TITRE_FR = "[FIX] portée : quelque chose"
|
||||||
|
|
||||||
|
|
||||||
|
class TestLesLangues(unittest.TestCase):
|
||||||
|
"""L'anglais d'abord, puis « --- FR --- » et le sujet traduit."""
|
||||||
|
|
||||||
|
def test_anglais_puis_francais_avec_titre_traduit(self):
|
||||||
|
corps = f"Why, in English.\n\n--- FR ---\n\n{TITRE_FR}\n\nPourquoi."
|
||||||
|
self.assertEqual([], check(_message(corps)))
|
||||||
|
|
||||||
|
def test_un_message_sans_marqueur_nest_pas_juge(self):
|
||||||
|
self.assertEqual([], check(_message("Why, in one language.")))
|
||||||
|
|
||||||
|
@EN_FRANCAIS
|
||||||
|
def test_le_marqueur_en_est_refuse(self):
|
||||||
|
corps = "Pourquoi, en français.\n\n--- EN ---\n\nWhy, in English."
|
||||||
|
problemes = check(_message(corps))
|
||||||
|
self.assertEqual(1, len(problemes))
|
||||||
|
self.assertIn("--- EN ---", problemes[0])
|
||||||
|
|
||||||
|
@EN_FRANCAIS
|
||||||
|
def test_la_section_francaise_sans_titre(self):
|
||||||
|
problemes = check(_message("Why.\n\n--- FR ---\n\nPourquoi."))
|
||||||
|
self.assertEqual(1, len(problemes))
|
||||||
|
self.assertIn("sujet traduit", problemes[0])
|
||||||
|
self.assertIn("[FIX] …", problemes[0])
|
||||||
|
|
||||||
|
def test_le_titre_traduit_porte_le_meme_tag(self):
|
||||||
|
corps = (
|
||||||
|
"Why.\n\n--- FR ---\n\n[ADD] portée : autre chose\n\nPourquoi."
|
||||||
|
)
|
||||||
|
self.assertEqual(1, len(check(_message(corps))))
|
||||||
|
|
||||||
|
@EN_FRANCAIS
|
||||||
|
def test_le_titre_traduit_suit_les_regles_du_sujet(self):
|
||||||
|
titre = "[FIX] portée : " + "é" * MAX
|
||||||
|
corps = f"Why.\n\n--- FR ---\n\n{titre}\n\nPourquoi."
|
||||||
|
problemes = check(_message(corps))
|
||||||
|
self.assertEqual(1, len(problemes))
|
||||||
|
self.assertTrue(problemes[0].startswith("sujet français :"), problemes[0])
|
||||||
|
|
||||||
|
def test_le_titre_traduit_ne_compte_pas_dans_le_corps(self):
|
||||||
|
moitie = "\n".join(f"ligne {n}" for n in range(MAX_BODY))
|
||||||
|
corps = f"{moitie}\n\n--- FR ---\n\n{TITRE_FR}\n{moitie}"
|
||||||
|
self.assertEqual([], check(_message(corps)))
|
||||||
|
|
||||||
|
|
||||||
class TestLeCorps(unittest.TestCase):
|
class TestLeCorps(unittest.TestCase):
|
||||||
|
|
@ -166,7 +214,7 @@ class TestLeCorps(unittest.TestCase):
|
||||||
|
|
||||||
def test_dix_lignes_par_langue_passent(self):
|
def test_dix_lignes_par_langue_passent(self):
|
||||||
moitie = "\n".join(f"ligne {n}" for n in range(MAX_BODY))
|
moitie = "\n".join(f"ligne {n}" for n in range(MAX_BODY))
|
||||||
corps = f"{moitie}\n\n--- FR ---\n\n{moitie}"
|
corps = f"{moitie}\n\n--- FR ---\n\n{TITRE_FR}\n\n{moitie}"
|
||||||
self.assertEqual([], check(_message(corps)))
|
self.assertEqual([], check(_message(corps)))
|
||||||
|
|
||||||
def test_onze_lignes_pour_une_langue_sont_refusees(self):
|
def test_onze_lignes_pour_une_langue_sont_refusees(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue