From 41987f8ea83190dd14ff579a2a7485567fad7f70 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 24 Sep 2026 18:42:22 +0200 Subject: [PATCH] i18n --- .gitignore | 11 ++ README.md | 50 +++-- box_core.py | 28 ++- box_maker.inx | 79 +++++--- box_maker.py | 44 ++++- deploy.ps1 | 32 ++- docs/parametres_dialogue.svg | 108 ---------- docs/schema_parametres.py | 141 +++++++++---- i18n.py | 304 +++++++++++++++++++++++++++++ images/parameters_en.png | Bin 0 -> 106601 bytes images/parametres.png | Bin 118419 -> 0 bytes locale/en/LC_MESSAGES/box_maker.mo | Bin 0 -> 3612 bytes locale/fr/LC_MESSAGES/box_maker.mo | Bin 0 -> 3831 bytes po/box_maker.pot | 165 ++++++++++++++++ po/en.po | 188 ++++++++++++++++++ po/fr.po | 190 ++++++++++++++++++ tests/test_extension.py | 128 ++++++++++++ 17 files changed, 1258 insertions(+), 210 deletions(-) create mode 100644 .gitignore delete mode 100644 docs/parametres_dialogue.svg create mode 100644 i18n.py create mode 100644 images/parameters_en.png delete mode 100644 images/parametres.png create mode 100644 locale/en/LC_MESSAGES/box_maker.mo create mode 100644 locale/fr/LC_MESSAGES/box_maker.mo create mode 100644 po/box_maker.pot create mode 100644 po/en.po create mode 100644 po/fr.po create mode 100644 tests/test_extension.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..057f18f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Cache Python et outils +__pycache__/* +*.pyc +.pytest_cache/ +.venv/ + +# Archives de distribution +*.zip + +# SVG intermediaires du schema (regeneres par docs/schema_parametres.py) +docs/*.svg diff --git a/README.md b/README.md index 126c34a..5986613 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Extension Inkscape 1.x qui génère le patron d'une boîte à découper au laser - Couleur et épaisseur du trait réglables (défaut : rouge, 0,1 mm) - Étiquettes optionnelles (texte bleu). Désactivées par défaut : le laser graverait le texte - Message d'erreur si une dimension est trop petite (minimum 5 × l'épaisseur en dimension extérieure) +- Interface **en français et en anglais** selon la langue d'Inkscape : libellés, onglet **Aide**, noms des pièces et messages d'erreur. Le schéma de l'onglet **Schéma** / **Diagram** est en anglais (Inkscape ne traduit pas les images) ## Installation @@ -49,14 +50,14 @@ Inkscape fermé, depuis le dossier du projet (PowerShell) : .\deploy.ps1 -Uninstall # désinstalle ``` -Le script copie `box_maker.inx`, `box_maker.py`, `box_core.py` et le dossier `images\` (schéma de l'onglet « Schéma »). Options : `-ExtensionsDir`, `-FolderName`, `-Force` (déploie même si Inkscape est ouvert). Redémarrer ensuite Inkscape. +Le script copie `box_maker.inx`, `box_maker.py`, `box_core.py`, `images\parameters_en.png` (schéma de la boîte de dialogue) et le dossier `locale\` (traductions compilées). Options : `-ExtensionsDir`, `-FolderName`, `-Force` (déploie même si Inkscape est ouvert). Redémarrer ensuite Inkscape. -Installation manuelle : copier ces mêmes fichiers et le dossier `images\` dans `%APPDATA%\inkscape\extensions\boxMaker\`. +Installation manuelle : copier ces mêmes fichiers et dossiers, en gardant l'arborescence, dans `%APPDATA%\inkscape\extensions\boxMaker\`. ## Utilisation -1. Menu **Extensions > AlexDesign > Boîte à créneaux…** -2. Régler les paramètres (illustrés dans l'onglet **Schéma**), cocher **Aperçu en direct** si besoin +1. Menu **Extensions > AlexDesign > Boîte à créneaux…** (en anglais : _Finger-Joint Box…_) +2. Régler les paramètres (illustrés dans l'onglet **Schéma**, résumés dans l'onglet **Aide**), cocher **Aperçu en direct** si besoin 3. **Appliquer** ### En ligne de commande @@ -133,39 +134,58 @@ Le kerf dépend de la matière : refaire la mesure en changeant de matériau ou ``` . -├── box_maker.inx # Interface de l'extension (paramètres, menu) -├── box_maker.py # Extension inkex : conversion en SVG, placement dans le calque +├── box_maker.inx # Interface de l'extension (textes source en anglais) +├── box_maker.py # Extension inkex : conversion en SVG, placement, textes traduits ├── box_core.py # Géométrie pure Python (mm) : créneaux, kerf, mise en page +├── i18n.py # Chaîne de traduction (extract / update / compile), Python pur +├── po/ # box_maker.pot, en.po, fr.po (seul fr.po se traduit à la main) +├── locale/ # Catalogues .mo compilés (générés, déployés) ├── deploy.ps1 # Installation / désinstallation dans Inkscape ├── images/ -│ └── parametres.png # Schéma de l'onglet « Schéma » (déployé) +│ └── parameters_en.png # Schéma de l'onglet « Diagram » (déployé) ├── docs/ # Illustrations du README (non déployées) │ ├── apercu-boite.png # Patron d'exemple -│ ├── parametres.png # Schéma des paramètres (+ sources .svg) -│ └── schema_parametres.py # Générateur des deux versions du schéma +│ ├── parametres.png # Schéma des paramètres en français +│ └── schema_parametres.py # Générateur du schéma fr et en └── tests/ - └── test_box_core.py # Tests unitaires de la géométrie + ├── test_box_core.py # Tests unitaires de la géométrie + └── test_extension.py # Traductions, cohérence .inx, bout en bout avec inkex ``` ## Développement -Environnement et tests (13 tests, géométrie pure, sans `inkex`) : +Environnement et tests : ```powershell python -m venv .venv .\.venv\Scripts\Activate.ps1 pip install pytest -python -m pytest -q +python -m pytest -q # 18 réussis, 3 ignorés (sans inkex) + +$env:PYTHONPATH = "C:\Program Files\Inkscape\share\inkscape\extensions" +python -m pytest -q # 21 réussis (avec inkex) ``` +Les tests couvrent la géométrie (`box_core`), les catalogues de traduction (complets, `.mo` à jour, champs de format conservés), la cohérence `.inx` ↔ `add_argument`, la présence des images de la boîte de dialogue et l'extension de bout en bout (6 pièces nommées, message d'erreur). + +### Traductions + +Les textes source (`.inx` et appels `_("...")` de `box_maker.py`) sont en anglais ; Inkscape les affiche traduits grâce à l'attribut `translationdomain="box_maker"` du `.inx`. + +```powershell +python i18n.py # extract + update + compile ; liste les chaînes à traduire +``` + +Après modification d'un texte : lancer `python i18n.py`, compléter les `msgstr` vides de `po/fr.po`, relancer `python i18n.py`, puis `.\deploy.ps1`. `po/en.po` se remplit tout seul ; ne jamais modifier les `.mo`. Pour une nouvelle langue : l'ajouter à `LANGUAGES` dans `i18n.py`, puis même démarche avec `po/.po`. + ### Schéma des paramètres `docs/schema_parametres.py` dessine le schéma avec les fonctions de `box_core.py` (il reste fidèle au rendu réel) et l'exporte en PNG via `C:\Program Files\Inkscape\bin\inkscape.com` : ```powershell -python docs/schema_parametres.py # les deux versions -python docs/schema_parametres.py readme # docs/parametres.png, 1400 px -python docs/schema_parametres.py dialogue # images/parametres.png, 900 px, textes agrandis +python docs/schema_parametres.py # les deux versions +python docs/schema_parametres.py fr # docs/parametres.png, 1400 px (README) +python docs/schema_parametres.py en # images/parameters_en.png, 900 px, textes agrandis (boîte de dialogue) ``` Relancer après toute modification de la géométrie ou des paramètres, puis `.\deploy.ps1`. diff --git a/box_core.py b/box_core.py index 8a5c075..0d70984 100644 --- a/box_core.py +++ b/box_core.py @@ -33,6 +33,27 @@ class Panel: return Panel(self.name, [(x + dx, y + dy) for x, y in self.points]) +_DIMENSION_NAMES = {"length": "longueur", "width": "largeur", "height": "hauteur"} + + +class DimensionTooSmall(ValueError): + """Dimension exterieure trop petite pour l'epaisseur. + + Les champs (dimension = "length" | "width" | "height", size, thickness, + minimum) permettent a l'extension de traduire le message. + """ + + def __init__(self, dimension, size, thickness): + self.dimension = dimension + self.size = size + self.thickness = thickness + self.minimum = 5 * thickness + super().__init__( + f"La {_DIMENSION_NAMES[dimension]} ({size:.1f} mm extérieure) est trop petite " + f"pour l'épaisseur ({thickness:g} mm) : il faut au moins {self.minimum:g} mm." + ) + + def finger_count(middle, finger): """Nombre impair (≥ 3) de créneaux sur une longueur utile `middle`.""" n = max(1, round(middle / finger)) @@ -119,13 +140,10 @@ def box_panels(length, width, height, thickness, finger=0.0, interior=False, lid width += 2 * t height += 2 * t if lid else t - for label, size in (("longueur", length), ("largeur", width), ("hauteur", height)): + for dimension, size in (("length", length), ("width", width), ("height", height)): middle = size - 2 * t if middle <= 0 or middle / finger_count(middle, finger) < t: - raise ValueError( - f"La {label} ({size:.1f} mm extérieure) est trop petite pour " - f"l'épaisseur ({t:g} mm) : il faut au moins {5 * t:g} mm." - ) + raise DimensionTooSmall(dimension, size, t) top = MALE if lid else PLAIN front = (top, MALE, MALE, MALE) diff --git a/box_maker.inx b/box_maker.inx index 66f9269..2dd8fd6 100644 --- a/box_maker.inx +++ b/box_maker.inx @@ -1,39 +1,70 @@ - - Boîte à créneaux + + + Finger-Joint Box fr.alex.box_maker - 100 - 80 - 50 - - - + 100 + 80 + 50 + + + - true + true - - 3 - 0 - 0.1 - - - + + 3 + 0 + 0.1 + + + - - 5 - 0.1 - 0xff0000ff - false + + 5 + 0.1 + 0xff0000ff + false - - - images/parametres.png + + + images/parameters_en.png + + + + diff --git a/box_maker.py b/box_maker.py index 7bbcf45..3b9f76c 100644 --- a/box_maker.py +++ b/box_maker.py @@ -7,9 +7,27 @@ pour pouvoir être sélectionnée et déplacée seule. import inkex from inkex import Group, PathElement, TextElement +from inkex.localization import localize import box_core +# Installe _() dans les builtins : catalogue « box_maker » de la langue de +# l'interface (Inkscape transmet domaine et dossier par l'environnement), +# textes anglais d'origine sinon. +localize() + + +def panel_names(): + """Noms affiches des pieces, indexes par leur nom dans box_core.""" + return { + "Avant": _("Front"), + "Arrière": _("Back"), + "Gauche": _("Left"), + "Droite": _("Right"), + "Fond": _("Bottom"), + "Couvercle": _("Lid"), + } + class BoxMaker(inkex.EffectExtension): def add_arguments(self, pars): @@ -35,8 +53,18 @@ class BoxMaker(inkex.EffectExtension): finger=opt.finger, kerf=opt.kerf, interior=opt.dim_type == "interior", lid=opt.lid, gap=opt.gap, ) - except ValueError as err: - raise inkex.AbortExtension(str(err)) + except box_core.DimensionTooSmall as err: + dimension = { + "length": _("length"), + "width": _("width"), + "height": _("height"), + }[err.dimension] + inkex.errormsg(_( + "The {dimension} ({size:.1f} mm exterior) is too small for the " + "thickness ({thickness:g} mm): it must be at least {minimum:g} mm." + ).format(dimension=dimension, size=err.size, thickness=err.thickness, + minimum=err.minimum)) + return mm = self._uu_per_mm() origin_x, origin_y = self._origin(panels, mm) @@ -50,10 +78,12 @@ class BoxMaker(inkex.EffectExtension): "stroke-width": opt.stroke_width * mm, } + names = panel_names() for panel in panels: + name = names[panel.name] path = PathElement() path.set_id(self.svg.get_unique_id("boite-")) - path.label = panel.name + path.label = name path.style = style path.path = "M " + " L ".join( f"{origin_x + x * mm:.4f},{origin_y + y * mm:.4f}" for x, y in panel.points @@ -62,10 +92,10 @@ class BoxMaker(inkex.EffectExtension): if opt.labels: group = Group() group.set_id(self.svg.get_unique_id("boite-")) - group.label = panel.name + group.label = name group.transform = to_layer group.add(path) - group.add(self._label(panel, origin_x, origin_y, mm)) + group.add(self._label(panel, name, origin_x, origin_y, mm)) layer.add(group) else: path.transform = to_layer @@ -97,7 +127,7 @@ class BoxMaker(inkex.EffectExtension): cx, cy = self.svg.get_page_bbox().center return cx - max_x / 2, cy - max_y / 2 - def _label(self, panel, origin_x, origin_y, mm): + def _label(self, panel, name, origin_x, origin_y, mm): min_x, min_y, max_x, max_y = panel.bbox() size = min(5.0, (max_y - min_y) / 4) * mm text = TextElement() @@ -110,7 +140,7 @@ class BoxMaker(inkex.EffectExtension): "fill": "#0000ff", "stroke": "none", } - text.text = panel.name + text.text = name return text diff --git a/deploy.ps1 b/deploy.ps1 index b9ae2a8..4471fac 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -1,10 +1,11 @@ -<# +<# .SYNOPSIS Déploie l'extension « Boîte à créneaux » dans Inkscape. .DESCRIPTION - Copie box_maker.inx, box_maker.py, box_core.py et images dans un - sous-dossier dédié du répertoire des extensions utilisateur d'Inkscape + Copie box_maker.inx, box_maker.py, box_core.py, le schéma de l'onglet + « Diagram » (images\parameters_en.png) et les traductions compilées + (locale\\LC_MESSAGES\box_maker.mo) dans un sous-dossier dédié du répertoire des extensions utilisateur d'Inkscape (Inkscape explore récursivement ce répertoire). Refuse de s'exécuter si Inkscape est ouvert : les extensions ne sont chargées qu'au démarrage. @@ -36,7 +37,8 @@ param( $ErrorActionPreference = 'Stop' -$files = @('box_maker.inx', 'box_maker.py', 'box_core.py') +$files = @('box_maker.inx', 'box_maker.py', 'box_core.py', 'images\parameters_en.png') +$localeDir = 'locale' $source = $PSScriptRoot $destination = Join-Path $ExtensionsDir $FolderName @@ -69,6 +71,9 @@ if ($Uninstall) { # --- Vérification des sources ------------------------------------------------ $missing = $files | Where-Object { -not (Test-Path -LiteralPath (Join-Path $source $_)) } +if (-not (Test-Path -LiteralPath (Join-Path $source $localeDir))) { + $missing = @($missing) + "$localeDir\ (lancer : python i18n.py)" +} if ($missing) { Write-Error "Fichier(s) introuvable(s) dans $source : $($missing -join ', ')" exit 1 @@ -89,20 +94,29 @@ if (-not (Test-Path -LiteralPath $destination)) { Write-Host "Dossier créé : $destination" } +# Images : le dossier est recréé (une image renommée ne reste pas en double) +$imagesTo = Join-Path $destination 'images' +if (Test-Path -LiteralPath $imagesTo) { + Remove-Item -LiteralPath $imagesTo -Recurse -Force +} + foreach ($file in $files) { $from = Join-Path $source $file $to = Join-Path $destination $file $action = if (Test-Path -LiteralPath $to) { 'remplacé' } else { 'copié ' } + New-Item -ItemType Directory -Path (Split-Path $to) -Force | Out-Null Copy-Item -LiteralPath $from -Destination $to -Force Write-Host " $action $file" } -# Schéma affiché dans l'onglet « Schéma » de la boîte de dialogue -$images = Join-Path $source 'images' -if (Test-Path -LiteralPath $images) { - Copy-Item -LiteralPath $images -Destination $destination -Recurse -Force - Write-Host " copié images\" +# Traductions : le dossier est remplacé en entier (langue retirée => supprimée) +$localeTo = Join-Path $destination $localeDir +if (Test-Path -LiteralPath $localeTo) { + Remove-Item -LiteralPath $localeTo -Recurse -Force } +Copy-Item -LiteralPath (Join-Path $source $localeDir) -Destination $localeTo -Recurse +$languages = Get-ChildItem -LiteralPath $localeTo -Directory | ForEach-Object Name +Write-Host " copié $localeDir\ ($($languages -join ', '))" # Le cache Python d'une version précédente peut masquer les fichiers copiés $pycache = Join-Path $destination '__pycache__' diff --git a/docs/parametres_dialogue.svg b/docs/parametres_dialogue.svg deleted file mode 100644 index 6270a69..0000000 --- a/docs/parametres_dialogue.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - -1 Dimensions de la boîte - - - -Avant -Couvercle - - - - - -Longueur - - - - - -Largeur - - - - - -Hauteur -Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces, -bord haut des côtés droit. Minimum : 5 × l'épaisseur. -2 Épaisseur et type de dimensions - - - -Fond - - - - - -Extérieure - - - - - -Intérieure - - - - - - -Épaisseur -Extérieures : cotes hors tout. Intérieures : l'extension ajoute -2 épaisseurs (1 seule en hauteur si la boîte est ouverte). -3 Largeur des créneaux - - -Couvercle -Avant - - - - - -Créneau - - - - - - -Profondeur = épaisseur -0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair -de créneaux par bord (ici 7 sur l'avant). -4 Compensation du kerf - - - - - - - - -Créneau voulu - - - - - -Dessiné : créneau + kerf - - - - - - -kerf / 2 - -trait dessiné - -cote après découpe -Le laser enlève kerf / 2 de chaque côté du trait : le contour -est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation. - diff --git a/docs/schema_parametres.py b/docs/schema_parametres.py index 8847849..d250932 100644 --- a/docs/schema_parametres.py +++ b/docs/schema_parametres.py @@ -3,10 +3,11 @@ """ Genere le schema explicatif des parametres de l'extension. - python docs/schema_parametres.py # les deux versions - python docs/schema_parametres.py readme # docs/parametres.svg + .png (README) - python docs/schema_parametres.py dialogue # images/parametres.png (onglet - # « Schéma » de la boite de dialogue) + python docs/schema_parametres.py # les deux versions + python docs/schema_parametres.py fr # docs/parametres.svg + .png (README) + python docs/schema_parametres.py en # docs/parameters_en.svg + # + images/parameters_en.png (onglet + # « Diagram » de la boite de dialogue) Les formes sont calculees par box_core (memes fonctions que l'extension), le schema reste donc fidele au resultat reel. L'export PNG passe par @@ -39,25 +40,85 @@ FONT = "font-family:Arial,Helvetica,sans-serif" INKSCAPE = [r"C:\Program Files\Inkscape\bin\inkscape.com", "/usr/bin/inkscape", "/Applications/Inkscape.app/Contents/MacOS/inkscape"] -# L'extension n'existe qu'en francais : deux versions du meme schema. -# La version « dialogue » est affichee reduite (900 px de large) dans la boite de -# dialogue : textes x 1.25 pour rester lisibles. +# Version par langue : textes, fichiers produits, largeur du PNG et agrandissement +# des textes (la version anglaise est affichee reduite dans la boite de dialogue : +# 900 px de large, textes x 1.25 pour rester lisibles). Inkscape ne traduit pas +# le chemin d'une image : la boite de dialogue montre la version anglaise. VERSIONS = { - "readme": { + "fr": { "svg": os.path.join(HERE, "parametres.svg"), "png": os.path.join(HERE, "parametres.png"), "png_width": 1400, "font_scale": 1.0, + "text": { + "title1": "Dimensions de la boîte", + "front": "Avant", + "lid": "Couvercle", + "length": "Longueur", + "width": "Largeur", + "height": "Hauteur", + "note1": ["Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,", + "bord haut des côtés droit. Minimum : 5 × l'épaisseur."], + "title2": "Épaisseur et type de dimensions", + "bottom": "Fond", + "exterior": "Extérieure", + "interior": "Intérieure", + "thickness": "Épaisseur", + "note2": ["Extérieures : cotes hors tout. Intérieures : l'extension ajoute", + "2 épaisseurs (1 seule en hauteur si la boîte est ouverte)."], + "title3": "Largeur des créneaux", + "finger": "Créneau", + "depth": "Profondeur = épaisseur", + "note3": ["0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair", + "de créneaux par bord (ici {} sur l'avant)."], + "title4": "Compensation du kerf", + "wanted": "Créneau voulu", + "drawn": "Dessiné : créneau + kerf", + "legend_drawn": "trait dessiné", + "legend_cut": "cote après découpe", + "note4": ["Le laser enlève kerf / 2 de chaque côté du trait : le contour", + "est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation."], + }, }, - "dialogue": { - "svg": os.path.join(HERE, "parametres_dialogue.svg"), - "png": os.path.join(ROOT, "images", "parametres.png"), + "en": { + "svg": os.path.join(HERE, "parameters_en.svg"), + "png": os.path.join(ROOT, "images", "parameters_en.png"), "png_width": 900, "font_scale": 1.25, + "text": { + "title1": "Box dimensions", + "front": "Front", + "lid": "Lid", + "length": "Length", + "width": "Width", + "height": "Height", + "note1": ["Lid (dashed): \"Closed box\". Unchecked: 5 parts, straight", + "top edge on the sides. Minimum: 5 × the thickness."], + "title2": "Thickness and dimension type", + "bottom": "Bottom", + "exterior": "Exterior", + "interior": "Interior", + "thickness": "Thickness", + "note2": ["Exterior: overall sizes. Interior: the extension adds", + "2 thicknesses (only 1 in height for an open box)."], + "title3": "Finger width", + "finger": "Finger", + "depth": "Depth = thickness", + "note3": ["0 = 3 × the thickness. The width is adjusted to get an", + "odd number of fingers per edge ({} on the front here)."], + "title4": "Kerf compensation", + "wanted": "Wanted finger", + "drawn": "Drawn: finger + kerf", + "legend_drawn": "drawn stroke", + "legend_cut": "size after cutting", + "note4": ["The laser removes kerf / 2 on each side of the stroke: the", + "outline is widened accordingly. Default 0.1 mm; 0 = none."], + }, }, } out = [] +T = {} FS = 1.0 @@ -139,7 +200,7 @@ def place(points, x0, y0, scale): def panel_1(): """Longueur, largeur, hauteur : boite en perspective cavaliere.""" - panel_title(30, 50, 1, "Dimensions de la boîte") + panel_title(30, 50, 1, T["title1"]) s = 3.2 # px par mm length, width, height = 100, 80, 50 dx, dy = 0.5 * math.cos(math.radians(35)), -0.5 * math.sin(math.radians(35)) @@ -154,24 +215,22 @@ def panel_1(): polygon([p(0, 0, Z), p(L, 0, Z), p(L, P, Z), p(0, P, Z)], EDGE, 2, "#e9ecef", 'stroke-dasharray="7,5"') polygon([p(L, 0, 0), p(L, P, 0), p(L, P, Z), p(L, 0, Z)], EDGE, 2, "#dee2e6") - text(*p(L / 2, 0, Z / 2 - 3), "Avant", size=16, color=GHOST, anchor="middle", halo=False) - text(*p(L / 2, P / 2, Z + 2), "Couvercle", size=16, color=GHOST, anchor="middle", + text(*p(L / 2, 0, Z / 2 - 3), T["front"], size=16, color=GHOST, anchor="middle", halo=False) + text(*p(L / 2, P / 2, Z + 2), T["lid"], size=16, color=GHOST, anchor="middle", halo=False) - dimension(p(0, 0, 0), p(L, 0, 0), 30, "Longueur") - dimension(p(L, 0, 0), p(L, P, 0), 34, "Largeur", label_shift=44) - dimension(p(0, 0, 0), p(0, 0, Z), -30, "Hauteur", label_shift=48) - note(30, 500, ["Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,", - "bord haut des côtés droit. Minimum : 5 × l'épaisseur."]) + dimension(p(0, 0, 0), p(L, 0, 0), 30, T["length"]) + dimension(p(L, 0, 0), p(L, P, 0), 34, T["width"], label_shift=44) + dimension(p(0, 0, 0), p(0, 0, Z), -30, T["height"], label_shift=48) + note(30, 500, T["note1"]) def panel_2(): """Epaisseur et dimensions interieures / exterieures : coupe verticale.""" ox = 700 - panel_title(ox + 30, 50, 2, "Épaisseur et type de dimensions") + panel_title(ox + 30, 50, 2, T["title2"]) s = 5.0 t, width, height = 6, 80, 44 # epaisseur exageree pour la lisibilite x0, y0 = ox + 150, 150 - hatch = 'fill="url(#hachures)"' def rect(x, y, w, h): polygon(place([(x, y), (x + w, y), (x + w, y + h), (x, y + h)], x0, y0, s), @@ -180,26 +239,24 @@ def panel_2(): rect(0, 0, t, height) # cote gauche rect(width - t, 0, t, height) # cote droit rect(0, height - t, width, t) # fond - del hatch - text(x0 + width / 2 * s, y0 + (height - t / 2) * s + 6, "Fond", size=15, + text(x0 + width / 2 * s, y0 + (height - t / 2) * s + 6, T["bottom"], size=15, color=INK, anchor="middle") top = y0 - dimension((x0, top), (x0 + width * s, top), -58, "Extérieure", label_shift=18) + dimension((x0, top), (x0 + width * s, top), -58, T["exterior"], label_shift=18) dimension((x0 + t * s, top + 60), (x0 + (width - t) * s, top + 60), 0, - "Intérieure", color=DIM2) + T["interior"], color=DIM2) dimension((x0 + (width - t) * s, y0 + 12 * s), (x0 + width * s, y0 + 12 * s), 0, "", color=DIM) - text(x0 + width * s + 16, y0 + 12 * s + 6, "Épaisseur", size=17, color=DIM, + text(x0 + width * s + 16, y0 + 12 * s + 6, T["thickness"], size=17, color=DIM, weight="bold") - note(ox + 30, 500, ["Extérieures : cotes hors tout. Intérieures : l'extension ajoute", - "2 épaisseurs (1 seule en hauteur si la boîte est ouverte)."]) + note(ox + 30, 500, T["note2"]) def panel_3(): """Creneaux : bord male de l'avant et bord femelle du couvercle.""" oy = 540 - panel_title(30, oy + 50, 3, "Largeur des créneaux") + panel_title(30, oy + 50, 3, T["title3"]) length, t = 100, 4 finger = 3 * t # valeur par defaut (parametre a 0) s = 5.6 @@ -221,25 +278,24 @@ def panel_3(): lid = [(x, 2 * lid_y - y) for x, y in edge(box_core.FEMALE, lid_y, 50)] polygon(lid, GHOST, 1.4, "#f8f9fa") polygon(edge(box_core.MALE, front_y, 150), EDGE, 2, FILL) - text(x0 + length * s + 12, lid_y - 20, "Couvercle", size=15, color=GHOST) - text(x0 + length * s + 12, front_y + 60, "Avant", size=15, color=GHOST) + text(x0 + length * s + 12, lid_y - 20, T["lid"], size=15, color=GHOST) + text(x0 + length * s + 12, front_y + 60, T["front"], size=15, color=GHOST) segs = box_core.edge_segments(length, t, finger, box_core.MALE) a, b, _ = segs[4] - dimension((x0 + a * s, front_y), (x0 + b * s, front_y), 50, "Créneau", label_shift=20) + dimension((x0 + a * s, front_y), (x0 + b * s, front_y), 50, T["finger"], label_shift=20) c, d, _ = segs[1] mid = x0 + (c + d) / 2 * s dimension((mid, front_y), (mid, front_y + t * s), 0, "", color=DIM2) - text(mid, front_y + t * s + 28, "Profondeur = épaisseur", size=15, color=DIM2, + text(mid, front_y + t * s + 28, T["depth"], size=15, color=DIM2, weight="bold", anchor="middle") - note(30, oy + 500, ["0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair", - "de créneaux par bord (ici {} sur l'avant).".format(len(segs))]) + note(30, oy + 500, [line.format(len(segs)) for line in T["note3"]]) def panel_4(): """Kerf : contour dessine decale de kerf / 2 vers l'exterieur.""" ox, oy = 700, 540 - panel_title(ox + 30, oy + 50, 4, "Compensation du kerf") + panel_title(ox + 30, oy + 50, 4, T["title4"]) t, finger, kerf = 4, 8, 1.2 # kerf exagere pour la lisibilite s = 18 x0, y0 = ox + 150, oy + 160 @@ -251,21 +307,20 @@ def panel_4(): k = kerf / 2 dimension((x0 + 6 * s, y0 + 0), (x0 + (6 + finger) * s, y0), 0, "") - text(x0 + (6 + finger / 2) * s, y0 + 36, "Créneau voulu", size=15, color=DIM, + text(x0 + (6 + finger / 2) * s, y0 + 36, T["wanted"], size=15, color=DIM, anchor="middle", weight="bold") dimension((x0 + (6 - k) * s, y0 - k * s), (x0 + (6 + finger + k) * s, y0 - k * s), -40, - "Dessiné : créneau + kerf", color=LASER, size=16) + T["drawn"], color=LASER, size=16) dimension((x0 + 22 * s, y0 + 6 * s), (x0 + (22 + k) * s, y0 + 6 * s), 0, "", color=DIM2) text(x0 + (22 + k) * s + 14, y0 + 6 * s + 6, "kerf / 2", size=16, color=DIM2, weight="bold") lx, ly = x0 + 10, y0 + 13 * s + 36 polyline([(lx, ly), (lx + 40, ly)], LASER, 2.2) - text(lx + 50, ly + 6, "trait dessiné", size=15, halo=False) + text(lx + 50, ly + 6, T["legend_drawn"], size=15, halo=False) polyline([(lx + 190, ly), (lx + 230, ly)], INK, 1.6, 'stroke-dasharray="6,4"') - text(lx + 240, ly + 6, "cote après découpe", size=15, halo=False) - note(ox + 30, oy + 500, ["Le laser enlève kerf / 2 de chaque côté du trait : le contour", - "est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation."]) + text(lx + 240, ly + 6, T["legend_cut"], size=15, halo=False) + note(ox + 30, oy + 500, T["note4"]) def export_png(svg_path, png_path, width): @@ -285,6 +340,8 @@ def build(name): global FS version = VERSIONS[name] out.clear() + T.clear() + T.update(version["text"]) FS = version["font_scale"] out.append('.pot depuis le .inx et les .py + python i18n.py update # reporte le modele dans po/.po + python i18n.py compile # po/.po -> locale//LC_MESSAGES/*.mo + +Le domaine est lu dans l'attribut translationdomain du .inx ; les sources sont +le .inx et tous les .py du dossier (hors i18n.py et tests). + +Les textes source (.inx et appels _() des .py) sont en anglais : c'est ce +qu'Inkscape affiche pour une langue sans catalogue. Le catalogue anglais est +rempli automatiquement avec les textes source ; seul fr.po se traduit a la main. + +Les chaines retenues dans le .inx suivent les regles d'Inkscape (inx.its) : +, attributs gui-text et gui-description, contenu de