i18n
This commit is contained in:
parent
5dc977f9dd
commit
41987f8ea8
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -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
|
||||
48
README.md
48
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/<langue>.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 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`.
|
||||
|
||||
28
box_core.py
28
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)
|
||||
|
||||
@ -1,39 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Boîte à créneaux</name>
|
||||
<!--
|
||||
Textes source en anglais, traduits par le catalogue « box_maker »
|
||||
(locale/<langue>/LC_MESSAGES/box_maker.mo). Après toute modification
|
||||
d'un texte : python i18n.py, puis compléter po/fr.po et relancer.
|
||||
-->
|
||||
<inkscape-extension translationdomain="box_maker"
|
||||
xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Finger-Joint Box</name>
|
||||
<id>fr.alex.box_maker</id>
|
||||
|
||||
<param name="tab" type="notebook">
|
||||
<page name="dimensions" gui-text="Dimensions">
|
||||
<param name="length" type="float" min="5" max="5000" precision="1" gui-text="Longueur (mm) :">100</param>
|
||||
<param name="width" type="float" min="5" max="5000" precision="1" gui-text="Largeur (mm) :">80</param>
|
||||
<param name="height" type="float" min="5" max="5000" precision="1" gui-text="Hauteur (mm) :">50</param>
|
||||
<param name="dim_type" type="optiongroup" appearance="combo" gui-text="Les dimensions sont :">
|
||||
<option value="exterior">Extérieures</option>
|
||||
<option value="interior">Intérieures</option>
|
||||
<param name="length" type="float" min="5" max="5000" precision="1" gui-text="Length (mm):">100</param>
|
||||
<param name="width" type="float" min="5" max="5000" precision="1" gui-text="Width (mm):">80</param>
|
||||
<param name="height" type="float" min="5" max="5000" precision="1" gui-text="Height (mm):">50</param>
|
||||
<param name="dim_type" type="optiongroup" appearance="combo" gui-text="Dimensions are:">
|
||||
<option value="exterior">Exterior</option>
|
||||
<option value="interior">Interior</option>
|
||||
</param>
|
||||
<param name="lid" type="bool" gui-text="Boîte fermée (avec couvercle)">true</param>
|
||||
<param name="lid" type="bool" gui-text="Closed box (with lid)">true</param>
|
||||
</page>
|
||||
|
||||
<page name="material" gui-text="Matière et créneaux">
|
||||
<param name="thickness" type="float" min="0.5" max="50" precision="2" gui-text="Épaisseur de la matière (mm) :">3</param>
|
||||
<param name="finger" type="float" min="0" max="500" precision="2" gui-text="Largeur des créneaux (mm, 0 = 3 × épaisseur) :">0</param>
|
||||
<param name="kerf" type="float" min="0" max="2" precision="3" gui-text="Compensation du trait laser, kerf (mm) :">0.1</param>
|
||||
<label appearance="header">Remarques</label>
|
||||
<label>La largeur des créneaux est ajustée pour obtenir un nombre impair de créneaux sur chaque bord.</label>
|
||||
<label>Le kerf élargit les créneaux et rétrécit les encoches pour un assemblage serré.</label>
|
||||
<page name="material" gui-text="Material and fingers">
|
||||
<param name="thickness" type="float" min="0.5" max="50" precision="2" gui-text="Material thickness (mm):">3</param>
|
||||
<param name="finger" type="float" min="0" max="500" precision="2" gui-text="Finger width (mm, 0 = 3 × thickness):">0</param>
|
||||
<param name="kerf" type="float" min="0" max="2" precision="3" gui-text="Laser beam compensation, kerf (mm):">0.1</param>
|
||||
<label appearance="header">Notes</label>
|
||||
<label>The finger width is adjusted to get an odd number of fingers on each edge.</label>
|
||||
<label>The kerf widens the fingers and narrows the slots for a tight fit.</label>
|
||||
</page>
|
||||
|
||||
<page name="drawing" gui-text="Tracé">
|
||||
<param name="gap" type="float" min="0" max="100" precision="1" gui-text="Espacement entre les pièces (mm) :">5</param>
|
||||
<param name="stroke_width" type="float" min="0.001" max="5" precision="3" gui-text="Épaisseur du trait (mm) :">0.1</param>
|
||||
<param name="stroke_color" type="color" gui-text="Couleur du trait :">0xff0000ff</param>
|
||||
<param name="labels" type="bool" gui-text="Nommer les pièces (texte gravé !)">false</param>
|
||||
<page name="drawing" gui-text="Drawing">
|
||||
<param name="gap" type="float" min="0" max="100" precision="1" gui-text="Spacing between parts (mm):">5</param>
|
||||
<param name="stroke_width" type="float" min="0.001" max="5" precision="3" gui-text="Stroke width (mm):">0.1</param>
|
||||
<param name="stroke_color" type="color" gui-text="Stroke color:">0xff0000ff</param>
|
||||
<param name="labels" type="bool" gui-text="Name the parts (engraved text!)">false</param>
|
||||
</page>
|
||||
|
||||
<!-- Image générée par docs/schema_parametres.py -->
|
||||
<page name="diagram" gui-text="Schéma">
|
||||
<image width="900" height="694">images/parametres.png</image>
|
||||
<!-- Schéma généré par docs/schema_parametres.py, en anglais :
|
||||
Inkscape ne traduit pas le chemin d'une image. -->
|
||||
<page name="diagram" gui-text="Diagram">
|
||||
<image width="900" height="694">images/parameters_en.png</image>
|
||||
</page>
|
||||
|
||||
<page name="help" gui-text="Help">
|
||||
<label xml:space="preserve">Draws the pattern of a laser-cut box whose
|
||||
panels interlock with straight finger joints.
|
||||
No selection is needed: the parts are added
|
||||
to the current layer, centered on the view.
|
||||
|
||||
Dimensions: exterior sizes, or interior sizes
|
||||
(the thickness is then added). Unchecking
|
||||
"Closed box" gives 5 parts, with a straight
|
||||
top edge on the sides.
|
||||
|
||||
Fingers: 0 = 3 × the thickness. The width is
|
||||
adjusted to get an odd number per edge.
|
||||
|
||||
Kerf: the outline is moved outwards by
|
||||
kerf / 2 so that parts keep their nominal size
|
||||
after cutting. Measure it: cut a 20 mm square,
|
||||
kerf = 20 − measured size. Larger = tighter.
|
||||
|
||||
Each part is a separate closed path, named
|
||||
(Front, Bottom…), that can be moved alone.
|
||||
Labels are text: the laser would engrave them.</label>
|
||||
</page>
|
||||
</param>
|
||||
|
||||
|
||||
44
box_maker.py
44
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
|
||||
|
||||
|
||||
|
||||
32
deploy.ps1
32
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\<langue>\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__'
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="1080" viewBox="0 0 1400 1080">
|
||||
<defs><pattern id="hachures" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="8" height="8" fill="#f1f3f5"/><line x1="0" y1="0" x2="0" y2="8" stroke="#adb5bd" stroke-width="1.5"/></pattern></defs>
|
||||
<rect width="1400" height="1080" fill="#ffffff"/>
|
||||
<line x1="700" y1="20" x2="700" y2="1060" stroke="#dee2e6" stroke-width="1.5"/>
|
||||
<line x1="20" y1="540" x2="1380" y2="540" stroke="#dee2e6" stroke-width="1.5"/>
|
||||
<text x="30" y="50" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">1 Dimensions de la boîte</text>
|
||||
<polygon points="150,365 470,365 470,205 150,205" fill="#f1f3f5" stroke="#212529" stroke-width="2" stroke-linejoin="miter" />
|
||||
<polygon points="150,205 470,205 574.85,131.58 254.85,131.58" fill="#e9ecef" stroke="#212529" stroke-width="2" stroke-linejoin="miter" stroke-dasharray="7,5"/>
|
||||
<polygon points="470,365 574.85,291.58 574.85,131.58 470,205" fill="#dee2e6" stroke="#212529" stroke-width="2" stroke-linejoin="miter" />
|
||||
<text x="310" y="294.6" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#adb5bd">Avant</text>
|
||||
<text x="362.43" y="161.89" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#adb5bd">Couvercle</text>
|
||||
<polyline points="150,369 150,401" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="470,369 470,401" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="150,395 470,395" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="150,395 159,390.95 159,399.05" fill="#d9480f"/>
|
||||
<polygon points="470,395 461,399.05 461,390.95" fill="#d9480f"/>
|
||||
<text x="310" y="425" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Longueur</text>
|
||||
<polyline points="472.29,368.28 492.94,397.77" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="577.15,294.86 597.79,324.35" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="489.5,392.85 594.35,319.43" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="489.5,392.85 494.55,384.37 499.2,391.01" fill="#d9480f"/>
|
||||
<polygon points="594.35,319.43 589.3,327.91 584.66,321.28" fill="#d9480f"/>
|
||||
<text x="573.47" y="408.7" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Largeur</text>
|
||||
<polyline points="146,365 114,365" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="146,205 114,205" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="120,365 120,205" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="120,365 115.95,356 124.05,356" fill="#d9480f"/>
|
||||
<polygon points="120,205 124.05,214 115.95,214" fill="#d9480f"/>
|
||||
<text x="60" y="292.5" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Hauteur</text>
|
||||
<text x="30" y="472.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,</text>
|
||||
<text x="30" y="500" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">bord haut des côtés droit. Minimum : 5 × l'épaisseur.</text>
|
||||
<text x="730" y="50" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">2 Épaisseur et type de dimensions</text>
|
||||
<polygon points="850,150 880,150 880,370 850,370" fill="url(#hachures)" stroke="#212529" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="1220,150 1250,150 1250,370 1220,370" fill="url(#hachures)" stroke="#212529" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="850,340 1250,340 1250,370 850,370" fill="url(#hachures)" stroke="#212529" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<text x="1050" y="361" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#343a40;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Fond</text>
|
||||
<polyline points="850,146 850,86" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1250,146 1250,86" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="850,92 1250,92" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="850,92 859,87.95 859,96.05" fill="#d9480f"/>
|
||||
<polygon points="1250,92 1241,96.05 1241,87.95" fill="#d9480f"/>
|
||||
<text x="1050" y="77" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Extérieure</text>
|
||||
<polyline points="880,214 880,216" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1220,214 1220,216" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="880,210 1220,210" fill="none" stroke="#1971c2" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="880,210 889,205.95 889,214.05" fill="#1971c2"/>
|
||||
<polygon points="1220,210 1211,214.05 1211,205.95" fill="#1971c2"/>
|
||||
<text x="1050" y="240" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Intérieure</text>
|
||||
<polyline points="1220,214 1220,216" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1250,214 1250,216" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1220,210 1250,210" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="1220,210 1229,205.95 1229,214.05" fill="#d9480f"/>
|
||||
<polygon points="1250,210 1241,214.05 1241,205.95" fill="#d9480f"/>
|
||||
<text x="1235" y="240" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
||||
<text x="1266" y="216" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Épaisseur</text>
|
||||
<text x="730" y="472.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">Extérieures : cotes hors tout. Intérieures : l'extension ajoute</text>
|
||||
<text x="730" y="500" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">2 épaisseurs (1 seule en hauteur si la boîte est ouverte).</text>
|
||||
<text x="30" y="590" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">3 Largeur des créneaux</text>
|
||||
<polygon points="70,640 70,667.6 92.4,667.6 92.4,667.6 166,667.6 166,690 239.6,690 239.6,667.6 313.2,667.6 313.2,690 386.8,690 386.8,667.6 460.4,667.6 460.4,690 534,690 534,667.6 607.6,667.6 607.6,667.6 630,667.6 630,640" fill="#f8f9fa" stroke="#adb5bd" stroke-width="1.4" stroke-linejoin="miter" />
|
||||
<polygon points="70,890 70,740 92.4,740 92.4,740 166,740 166,762.4 239.6,762.4 239.6,740 313.2,740 313.2,762.4 386.8,762.4 386.8,740 460.4,740 460.4,762.4 534,762.4 534,740 607.6,740 607.6,740 630,740 630,890" fill="#f1f3f5" stroke="#212529" stroke-width="2" stroke-linejoin="miter" />
|
||||
<text x="642" y="670" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#adb5bd;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Couvercle</text>
|
||||
<text x="642" y="800" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#adb5bd;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Avant</text>
|
||||
<polyline points="386.8,744 386.8,796" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="460.4,744 460.4,796" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="386.8,790 460.4,790" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="386.8,790 395.8,785.95 395.8,794.05" fill="#d9480f"/>
|
||||
<polygon points="460.4,790 451.4,794.05 451.4,785.95" fill="#d9480f"/>
|
||||
<text x="423.6" y="822.5" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Créneau</text>
|
||||
<polyline points="198.8,740 196.8,740" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="198.8,762.4 196.8,762.4" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="202.8,740 202.8,762.4" fill="none" stroke="#1971c2" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="202.8,740 206.85,749 198.75,749" fill="#1971c2"/>
|
||||
<polygon points="202.8,762.4 198.75,753.4 206.85,753.4" fill="#1971c2"/>
|
||||
<text x="180.3" y="758.7" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
||||
<text x="202.8" y="790.4" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Profondeur = épaisseur</text>
|
||||
<text x="30" y="1012.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair</text>
|
||||
<text x="30" y="1040" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">de créneaux par bord (ici 7 sur l'avant).</text>
|
||||
<text x="730" y="590" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">4 Compensation du kerf</text>
|
||||
<polygon points="850,772 958,772 958,700 1102,700 1102,772 1246,772 1246,934 850,934" fill="#f1f3f5" stroke="#343a40" stroke-width="1.6" stroke-linejoin="miter" stroke-dasharray="6,4"/>
|
||||
<polygon points="839.2,761.2 947.2,761.2 947.2,689.2 1112.8,689.2 1112.8,761.2 1256.8,761.2 1256.8,944.8 839.2,944.8" fill="none" stroke="#e03131" stroke-width="2.2" stroke-linejoin="miter" />
|
||||
<polyline points="958,704 958,706" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1102,704 1102,706" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="958,700 1102,700" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="958,700 967,695.95 967,704.05" fill="#d9480f"/>
|
||||
<polygon points="1102,700 1093,704.05 1093,695.95" fill="#d9480f"/>
|
||||
<text x="1030" y="730" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
||||
<text x="1030" y="736" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Créneau voulu</text>
|
||||
<polyline points="947.2,685.2 947.2,643.2" fill="none" stroke="#e03131" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1112.8,685.2 1112.8,643.2" fill="none" stroke="#e03131" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="947.2,649.2 1112.8,649.2" fill="none" stroke="#e03131" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="947.2,649.2 956.2,645.15 956.2,653.25" fill="#e03131"/>
|
||||
<polygon points="1112.8,649.2 1103.8,653.25 1103.8,645.15" fill="#e03131"/>
|
||||
<text x="1030" y="634.2" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:bold;fill:#e03131;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Dessiné : créneau + kerf</text>
|
||||
<polyline points="1246,812 1246,814" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1256.8,812 1256.8,814" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
||||
<polyline points="1246,808 1256.8,808" fill="none" stroke="#1971c2" stroke-width="1.6" stroke-linejoin="miter" />
|
||||
<polygon points="1246,808 1255,803.95 1255,812.05" fill="#1971c2"/>
|
||||
<polygon points="1256.8,808 1247.8,812.05 1247.8,803.95" fill="#1971c2"/>
|
||||
<text x="1251.4" y="838" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
||||
<text x="1270.8" y="814" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">kerf / 2</text>
|
||||
<polyline points="860,970 900,970" fill="none" stroke="#e03131" stroke-width="2.2" stroke-linejoin="miter" />
|
||||
<text x="910" y="976" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#343a40">trait dessiné</text>
|
||||
<polyline points="1050,970 1090,970" fill="none" stroke="#343a40" stroke-width="1.6" stroke-linejoin="miter" stroke-dasharray="6,4"/>
|
||||
<text x="1100" y="976" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#343a40">cote après découpe</text>
|
||||
<text x="730" y="1012.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">Le laser enlève kerf / 2 de chaque côté du trait : le contour</text>
|
||||
<text x="730" y="1040" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation.</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 15 KiB |
@ -4,9 +4,10 @@
|
||||
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 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('<defs><pattern id="hachures" width="8" height="8" '
|
||||
|
||||
304
i18n.py
Normal file
304
i18n.py
Normal file
@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python3
|
||||
# coding=utf-8
|
||||
"""
|
||||
Chaine de traduction de l'extension, sans dependance a gettext.
|
||||
|
||||
python i18n.py # extract + update + compile
|
||||
python i18n.py extract # po/<domaine>.pot depuis le .inx et les .py
|
||||
python i18n.py update # reporte le modele dans po/<langue>.po
|
||||
python i18n.py compile # po/<langue>.po -> locale/<langue>/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) :
|
||||
<name>, attributs gui-text et gui-description, contenu de <option>, <item> et
|
||||
<label>, sauf translatable="no". Les espaces sont normalises, sauf sous
|
||||
xml:space="preserve".
|
||||
"""
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
LANGUAGES = ["en", "fr"]
|
||||
SOURCE_LANGUAGE = "en"
|
||||
PO_DIR = os.path.join(HERE, "po")
|
||||
LOCALE_DIR = os.path.join(HERE, "locale")
|
||||
|
||||
XML_SPACE = "{http://www.w3.org/XML/1998/namespace}space"
|
||||
INX_NS = "{http://www.inkscape.org/namespace/inkscape/extension}"
|
||||
|
||||
|
||||
def _find_inx():
|
||||
found = sorted(name for name in os.listdir(HERE) if name.endswith(".inx"))
|
||||
if len(found) != 1:
|
||||
sys.exit("i18n.py attend un seul fichier .inx dans {} (trouve : {})".format(
|
||||
HERE, found))
|
||||
return found[0]
|
||||
|
||||
|
||||
INX = _find_inx()
|
||||
_INX_ROOT = ET.parse(os.path.join(HERE, INX)).getroot()
|
||||
DOMAIN = _INX_ROOT.get("translationdomain")
|
||||
if not DOMAIN:
|
||||
sys.exit("Attribut translationdomain absent de " + INX)
|
||||
EXTENSION_NAME = (_INX_ROOT.findtext(INX_NS + "name") or DOMAIN).strip()
|
||||
SOURCES = [INX] + sorted(
|
||||
name for name in os.listdir(HERE)
|
||||
if name.endswith(".py") and name != "i18n.py" and not name.startswith("test"))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Extraction
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def _local(tag):
|
||||
return tag.rsplit("}", 1)[-1]
|
||||
|
||||
|
||||
def _normalize(text, preserve):
|
||||
return text if preserve else " ".join(text.split())
|
||||
|
||||
|
||||
def extract_inx(path):
|
||||
"""Chaines traduisibles d'un fichier .inx, dans l'ordre du document."""
|
||||
messages = []
|
||||
|
||||
def walk(elem, preserve):
|
||||
preserve = elem.get(XML_SPACE, "preserve" if preserve else "default") == "preserve"
|
||||
if elem.get("translatable") != "no":
|
||||
for attr in ("gui-text", "gui-description"):
|
||||
if elem.get(attr):
|
||||
messages.append(_normalize(elem.get(attr), preserve))
|
||||
if _local(elem.tag) in ("name", "option", "item", "label") and elem.text:
|
||||
messages.append(_normalize(elem.text, preserve))
|
||||
for child in elem:
|
||||
walk(child, preserve)
|
||||
|
||||
walk(ET.parse(path).getroot(), False)
|
||||
return [m for m in messages if m]
|
||||
|
||||
|
||||
def extract_py(path):
|
||||
"""Arguments litteraux des appels _("...") d'un script Python."""
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
tree = ast.parse(handle.read(), path)
|
||||
calls = [node for node in ast.walk(tree)
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "_" and len(node.args) == 1
|
||||
and isinstance(node.args[0], ast.Constant)
|
||||
and isinstance(node.args[0].value, str)]
|
||||
calls.sort(key=lambda node: (node.lineno, node.col_offset))
|
||||
return [node.args[0].value for node in calls]
|
||||
|
||||
|
||||
def extract():
|
||||
"""Liste ordonnee et sans doublon de (msgid, [fichiers sources])."""
|
||||
found = {}
|
||||
for name in SOURCES:
|
||||
path = os.path.join(HERE, name)
|
||||
strings = extract_inx(path) if name.endswith(".inx") else extract_py(path)
|
||||
for msgid in strings:
|
||||
found.setdefault(msgid, [])
|
||||
if name not in found[msgid]:
|
||||
found[msgid].append(name)
|
||||
return list(found.items())
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Lecture / ecriture des fichiers .po
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
_ESCAPES = {"n": "\n", "t": "\t", '"': '"', "\\": "\\"}
|
||||
|
||||
|
||||
def _unquote(line):
|
||||
body = line.strip()[1:-1]
|
||||
return re.sub(r'\\(.)', lambda m: _ESCAPES.get(m.group(1), m.group(1)), body)
|
||||
|
||||
|
||||
def _quote(text):
|
||||
text = text.replace("\\", "\\\\").replace('"', '\\"').replace("\t", "\\t")
|
||||
lines = text.split("\n")
|
||||
parts = [line + "\\n" for line in lines[:-1]]
|
||||
if lines[-1]:
|
||||
parts.append(lines[-1])
|
||||
if len(parts) <= 1:
|
||||
return '"{}"'.format(parts[0] if parts else "")
|
||||
return '""\n' + "\n".join('"{}"'.format(part) for part in parts)
|
||||
|
||||
|
||||
def read_po(path):
|
||||
"""Dictionnaire msgid -> (msgstr, fuzzy). L'en-tete a pour cle ""."""
|
||||
entries = {}
|
||||
if not os.path.exists(path):
|
||||
return entries
|
||||
msgid = msgstr = None
|
||||
field = None
|
||||
fuzzy = False
|
||||
|
||||
def flush():
|
||||
if msgid is not None:
|
||||
entries[msgid] = (msgstr or "", fuzzy)
|
||||
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
for raw in handle:
|
||||
line = raw.strip()
|
||||
if line.startswith("#,") and "fuzzy" in line:
|
||||
flush()
|
||||
msgid = msgstr = field = None
|
||||
fuzzy = True
|
||||
elif line.startswith("msgid "):
|
||||
if field == "msgstr":
|
||||
flush()
|
||||
fuzzy = False
|
||||
msgid, msgstr, field = _unquote(line[6:]), None, "msgid"
|
||||
elif line.startswith("msgstr "):
|
||||
msgstr, field = _unquote(line[7:]), "msgstr"
|
||||
elif line.startswith('"') and field == "msgid":
|
||||
msgid += _unquote(line)
|
||||
elif line.startswith('"') and field == "msgstr":
|
||||
msgstr += _unquote(line)
|
||||
elif not line and field == "msgstr":
|
||||
flush()
|
||||
msgid = msgstr = field = None
|
||||
fuzzy = False
|
||||
flush()
|
||||
return entries
|
||||
|
||||
|
||||
def _header(language):
|
||||
fields = [
|
||||
("Project-Id-Version", DOMAIN),
|
||||
("Language", language or ""),
|
||||
("MIME-Version", "1.0"),
|
||||
("Content-Type", "text/plain; charset=UTF-8"),
|
||||
("Content-Transfer-Encoding", "8bit"),
|
||||
("Plural-Forms", {"fr": "nplurals=2; plural=(n > 1);",
|
||||
"en": "nplurals=2; plural=(n != 1);"}.get(language, "")),
|
||||
]
|
||||
return "".join("{}: {}\n".format(key, value) for key, value in fields if value)
|
||||
|
||||
|
||||
def write_po(path, language, messages, translations):
|
||||
title = ("Modele de traduction" if language is None
|
||||
else "Traduction ({})".format(language))
|
||||
out = ["# {} de l'extension Inkscape « {} ».".format(title, EXTENSION_NAME),
|
||||
"# Genere par i18n.py ; ne modifier que les msgstr.",
|
||||
"msgid \"\"",
|
||||
"msgstr " + _quote(_header(language)),
|
||||
""]
|
||||
for msgid, refs in messages:
|
||||
msgstr, fuzzy = translations.get(msgid, ("", False))
|
||||
out.append("#: " + " ".join(refs))
|
||||
if fuzzy:
|
||||
out.append("#, fuzzy")
|
||||
out.append("msgid " + _quote(msgid))
|
||||
out.append("msgstr " + _quote(msgstr))
|
||||
out.append("")
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as handle:
|
||||
handle.write("\n".join(out))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Compilation .mo (format GNU gettext)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def write_mo(path, catalog):
|
||||
"""Ecrit un catalogue {msgid: msgstr} au format .mo, cles triees."""
|
||||
keys = sorted(catalog)
|
||||
ids = [key.encode("utf-8") for key in keys]
|
||||
strs = [catalog[key].encode("utf-8") for key in keys]
|
||||
count = len(keys)
|
||||
ids_start = 7 * 4 + 16 * count
|
||||
strs_start = ids_start + sum(len(b) + 1 for b in ids)
|
||||
|
||||
table_ids, table_strs = [], []
|
||||
offset = ids_start
|
||||
for data in ids:
|
||||
table_ids += [len(data), offset]
|
||||
offset += len(data) + 1
|
||||
offset = strs_start
|
||||
for data in strs:
|
||||
table_strs += [len(data), offset]
|
||||
offset += len(data) + 1
|
||||
|
||||
output = struct.pack("<7I", 0x950412DE, 0, count, 7 * 4, 7 * 4 + 8 * count, 0, 0)
|
||||
output += struct.pack("<{}I".format(2 * count), *table_ids)
|
||||
output += struct.pack("<{}I".format(2 * count), *table_strs)
|
||||
output += b"".join(data + b"\0" for data in ids)
|
||||
output += b"".join(data + b"\0" for data in strs)
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(output)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Commandes
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def po_path(language):
|
||||
return os.path.join(PO_DIR, "{}.po".format(language))
|
||||
|
||||
|
||||
def mo_path(language):
|
||||
return os.path.join(LOCALE_DIR, language, "LC_MESSAGES", DOMAIN + ".mo")
|
||||
|
||||
|
||||
def cmd_extract():
|
||||
os.makedirs(PO_DIR, exist_ok=True)
|
||||
messages = extract()
|
||||
write_po(os.path.join(PO_DIR, DOMAIN + ".pot"), None, messages, {})
|
||||
print("{} chaines -> po/{}.pot".format(len(messages), DOMAIN))
|
||||
|
||||
|
||||
def cmd_update():
|
||||
messages = extract()
|
||||
for language in LANGUAGES:
|
||||
translations = read_po(po_path(language))
|
||||
if language == SOURCE_LANGUAGE:
|
||||
translations = {msgid: (msgid, False) for msgid, _refs in messages}
|
||||
write_po(po_path(language), language, messages, translations)
|
||||
missing = [m for m, _r in messages if not translations.get(m, ("", False))[0]]
|
||||
print("po/{}.po : {} a traduire".format(language, len(missing)))
|
||||
for msgid in missing:
|
||||
print(" " + msgid.splitlines()[0])
|
||||
|
||||
|
||||
def cmd_compile():
|
||||
for language in LANGUAGES:
|
||||
entries = read_po(po_path(language))
|
||||
catalog = {msgid: msgstr for msgid, (msgstr, fuzzy) in entries.items()
|
||||
if msgid and msgstr and not fuzzy}
|
||||
catalog[""] = _header(language)
|
||||
write_mo(mo_path(language), catalog)
|
||||
print("{} -> {} ({} chaines)".format(
|
||||
os.path.relpath(po_path(language), HERE),
|
||||
os.path.relpath(mo_path(language), HERE), len(catalog) - 1))
|
||||
|
||||
|
||||
def main(argv):
|
||||
commands = {"extract": [cmd_extract], "update": [cmd_update],
|
||||
"compile": [cmd_compile],
|
||||
"all": [cmd_extract, cmd_update, cmd_compile]}
|
||||
name = argv[0] if argv else "all"
|
||||
if name not in commands:
|
||||
print(__doc__)
|
||||
return 1
|
||||
for command in commands[name]:
|
||||
command()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
BIN
images/parameters_en.png
Normal file
BIN
images/parameters_en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 116 KiB |
BIN
locale/en/LC_MESSAGES/box_maker.mo
Normal file
BIN
locale/en/LC_MESSAGES/box_maker.mo
Normal file
Binary file not shown.
BIN
locale/fr/LC_MESSAGES/box_maker.mo
Normal file
BIN
locale/fr/LC_MESSAGES/box_maker.mo
Normal file
Binary file not shown.
165
po/box_maker.pot
Normal file
165
po/box_maker.pot
Normal file
@ -0,0 +1,165 @@
|
||||
# Modele de traduction de l'extension Inkscape « Finger-Joint Box ».
|
||||
# Genere par i18n.py ; ne modifier que les msgstr.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: box_maker\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Finger-Joint Box"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Dimensions"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Length (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Width (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Height (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Dimensions are:"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Exterior"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Interior"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Closed box (with lid)"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Material and fingers"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Material thickness (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Finger width (mm, 0 = 3 × thickness):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Laser beam compensation, kerf (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Drawing"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Spacing between parts (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Stroke width (mm):"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Stroke color:"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Name the parts (engraved text!)"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Diagram"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.inx
|
||||
msgid ""
|
||||
"Draws the pattern of a laser-cut box whose\n"
|
||||
"panels interlock with straight finger joints.\n"
|
||||
"No selection is needed: the parts are added\n"
|
||||
"to the current layer, centered on the view.\n"
|
||||
"\n"
|
||||
"Dimensions: exterior sizes, or interior sizes\n"
|
||||
"(the thickness is then added). Unchecking\n"
|
||||
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||
"top edge on the sides.\n"
|
||||
"\n"
|
||||
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||
"adjusted to get an odd number per edge.\n"
|
||||
"\n"
|
||||
"Kerf: the outline is moved outwards by\n"
|
||||
"kerf / 2 so that parts keep their nominal size\n"
|
||||
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||
"\n"
|
||||
"Each part is a separate closed path, named\n"
|
||||
"(Front, Bottom…), that can be moved alone.\n"
|
||||
"Labels are text: the laser would engrave them."
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Lid"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "length"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "width"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "height"
|
||||
msgstr ""
|
||||
|
||||
#: box_maker.py
|
||||
msgid "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||
msgstr ""
|
||||
188
po/en.po
Normal file
188
po/en.po
Normal file
@ -0,0 +1,188 @@
|
||||
# Traduction (en) de l'extension Inkscape « Finger-Joint Box ».
|
||||
# Genere par i18n.py ; ne modifier que les msgstr.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: box_maker\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Finger-Joint Box"
|
||||
msgstr "Finger-Joint Box"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Dimensions"
|
||||
msgstr "Dimensions"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Length (mm):"
|
||||
msgstr "Length (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Width (mm):"
|
||||
msgstr "Width (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Height (mm):"
|
||||
msgstr "Height (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Dimensions are:"
|
||||
msgstr "Dimensions are:"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Exterior"
|
||||
msgstr "Exterior"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Interior"
|
||||
msgstr "Interior"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Closed box (with lid)"
|
||||
msgstr "Closed box (with lid)"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Material and fingers"
|
||||
msgstr "Material and fingers"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Material thickness (mm):"
|
||||
msgstr "Material thickness (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Finger width (mm, 0 = 3 × thickness):"
|
||||
msgstr "Finger width (mm, 0 = 3 × thickness):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Laser beam compensation, kerf (mm):"
|
||||
msgstr "Laser beam compensation, kerf (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Notes"
|
||||
msgstr "Notes"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||
msgstr "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||
msgstr "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Drawing"
|
||||
msgstr "Drawing"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Spacing between parts (mm):"
|
||||
msgstr "Spacing between parts (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Stroke width (mm):"
|
||||
msgstr "Stroke width (mm):"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Stroke color:"
|
||||
msgstr "Stroke color:"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Name the parts (engraved text!)"
|
||||
msgstr "Name the parts (engraved text!)"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Diagram"
|
||||
msgstr "Diagram"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Help"
|
||||
msgstr "Help"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid ""
|
||||
"Draws the pattern of a laser-cut box whose\n"
|
||||
"panels interlock with straight finger joints.\n"
|
||||
"No selection is needed: the parts are added\n"
|
||||
"to the current layer, centered on the view.\n"
|
||||
"\n"
|
||||
"Dimensions: exterior sizes, or interior sizes\n"
|
||||
"(the thickness is then added). Unchecking\n"
|
||||
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||
"top edge on the sides.\n"
|
||||
"\n"
|
||||
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||
"adjusted to get an odd number per edge.\n"
|
||||
"\n"
|
||||
"Kerf: the outline is moved outwards by\n"
|
||||
"kerf / 2 so that parts keep their nominal size\n"
|
||||
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||
"\n"
|
||||
"Each part is a separate closed path, named\n"
|
||||
"(Front, Bottom…), that can be moved alone.\n"
|
||||
"Labels are text: the laser would engrave them."
|
||||
msgstr ""
|
||||
"Draws the pattern of a laser-cut box whose\n"
|
||||
"panels interlock with straight finger joints.\n"
|
||||
"No selection is needed: the parts are added\n"
|
||||
"to the current layer, centered on the view.\n"
|
||||
"\n"
|
||||
"Dimensions: exterior sizes, or interior sizes\n"
|
||||
"(the thickness is then added). Unchecking\n"
|
||||
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||
"top edge on the sides.\n"
|
||||
"\n"
|
||||
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||
"adjusted to get an odd number per edge.\n"
|
||||
"\n"
|
||||
"Kerf: the outline is moved outwards by\n"
|
||||
"kerf / 2 so that parts keep their nominal size\n"
|
||||
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||
"\n"
|
||||
"Each part is a separate closed path, named\n"
|
||||
"(Front, Bottom…), that can be moved alone.\n"
|
||||
"Labels are text: the laser would engrave them."
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Front"
|
||||
msgstr "Front"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Back"
|
||||
msgstr "Back"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Left"
|
||||
msgstr "Left"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Right"
|
||||
msgstr "Right"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Bottom"
|
||||
msgstr "Bottom"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Lid"
|
||||
msgstr "Lid"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "length"
|
||||
msgstr "length"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "width"
|
||||
msgstr "width"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "height"
|
||||
msgstr "height"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||
msgstr "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||
190
po/fr.po
Normal file
190
po/fr.po
Normal file
@ -0,0 +1,190 @@
|
||||
# Traduction (fr) de l'extension Inkscape « Finger-Joint Box ».
|
||||
# Genere par i18n.py ; ne modifier que les msgstr.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: box_maker\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Finger-Joint Box"
|
||||
msgstr "Boîte à créneaux"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Dimensions"
|
||||
msgstr "Dimensions"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Length (mm):"
|
||||
msgstr "Longueur (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Width (mm):"
|
||||
msgstr "Largeur (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Height (mm):"
|
||||
msgstr "Hauteur (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Dimensions are:"
|
||||
msgstr "Les dimensions sont :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Exterior"
|
||||
msgstr "Extérieures"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Interior"
|
||||
msgstr "Intérieures"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Closed box (with lid)"
|
||||
msgstr "Boîte fermée (avec couvercle)"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Material and fingers"
|
||||
msgstr "Matière et créneaux"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Material thickness (mm):"
|
||||
msgstr "Épaisseur de la matière (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Finger width (mm, 0 = 3 × thickness):"
|
||||
msgstr "Largeur des créneaux (mm, 0 = 3 × épaisseur) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Laser beam compensation, kerf (mm):"
|
||||
msgstr "Compensation du trait laser, kerf (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Notes"
|
||||
msgstr "Remarques"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||
msgstr "La largeur des créneaux est ajustée pour obtenir un nombre impair de créneaux sur chaque bord."
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||
msgstr "Le kerf élargit les créneaux et rétrécit les encoches pour un assemblage serré."
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Drawing"
|
||||
msgstr "Tracé"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Spacing between parts (mm):"
|
||||
msgstr "Espacement entre les pièces (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Stroke width (mm):"
|
||||
msgstr "Épaisseur du trait (mm) :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Stroke color:"
|
||||
msgstr "Couleur du trait :"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Name the parts (engraved text!)"
|
||||
msgstr "Nommer les pièces (texte gravé !)"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Diagram"
|
||||
msgstr "Schéma"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid "Help"
|
||||
msgstr "Aide"
|
||||
|
||||
#: box_maker.inx
|
||||
msgid ""
|
||||
"Draws the pattern of a laser-cut box whose\n"
|
||||
"panels interlock with straight finger joints.\n"
|
||||
"No selection is needed: the parts are added\n"
|
||||
"to the current layer, centered on the view.\n"
|
||||
"\n"
|
||||
"Dimensions: exterior sizes, or interior sizes\n"
|
||||
"(the thickness is then added). Unchecking\n"
|
||||
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||
"top edge on the sides.\n"
|
||||
"\n"
|
||||
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||
"adjusted to get an odd number per edge.\n"
|
||||
"\n"
|
||||
"Kerf: the outline is moved outwards by\n"
|
||||
"kerf / 2 so that parts keep their nominal size\n"
|
||||
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||
"\n"
|
||||
"Each part is a separate closed path, named\n"
|
||||
"(Front, Bottom…), that can be moved alone.\n"
|
||||
"Labels are text: the laser would engrave them."
|
||||
msgstr ""
|
||||
"Dessine le patron d'une boîte à découper au laser,\n"
|
||||
"dont les faces s'assemblent par créneaux droits.\n"
|
||||
"Aucune sélection nécessaire : les pièces sont\n"
|
||||
"ajoutées au calque courant, centrées sur la vue.\n"
|
||||
"\n"
|
||||
"Dimensions : extérieures, ou intérieures\n"
|
||||
"(l'épaisseur est alors ajoutée). Décocher\n"
|
||||
"« Boîte fermée » donne 5 pièces, bord haut\n"
|
||||
"des côtés droit.\n"
|
||||
"\n"
|
||||
"Créneaux : 0 = 3 × l'épaisseur. La largeur est\n"
|
||||
"ajustée pour un nombre impair par bord.\n"
|
||||
"\n"
|
||||
"Kerf : le contour est décalé vers l'extérieur\n"
|
||||
"de kerf / 2 : les pièces gardent leurs cotes\n"
|
||||
"après découpe. Mesure : découper un carré de\n"
|
||||
"20 mm, kerf = 20 − cote mesurée. Plus grand =\n"
|
||||
"plus serré.\n"
|
||||
"\n"
|
||||
"Chaque pièce est un chemin fermé indépendant,\n"
|
||||
"nommé (Avant, Fond…), déplaçable seul.\n"
|
||||
"Les étiquettes sont du texte : le laser les\n"
|
||||
"graverait."
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Front"
|
||||
msgstr "Avant"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Back"
|
||||
msgstr "Arrière"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Left"
|
||||
msgstr "Gauche"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Right"
|
||||
msgstr "Droite"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Bottom"
|
||||
msgstr "Fond"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "Lid"
|
||||
msgstr "Couvercle"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "length"
|
||||
msgstr "longueur"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "width"
|
||||
msgstr "largeur"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "height"
|
||||
msgstr "hauteur"
|
||||
|
||||
#: box_maker.py
|
||||
msgid "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||
msgstr "La {dimension} ({size:.1f} mm extérieure) est trop petite pour l'épaisseur ({thickness:g} mm) : il faut au moins {minimum:g} mm."
|
||||
128
tests/test_extension.py
Normal file
128
tests/test_extension.py
Normal file
@ -0,0 +1,128 @@
|
||||
# coding=utf-8
|
||||
"""Tests de la boite de dialogue, des traductions et de l'extension (pytest)."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
INX = os.path.join(ROOT, "box_maker.inx")
|
||||
|
||||
import box_core # noqa: E402
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Traductions
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def test_translations_up_to_date_and_complete():
|
||||
"""Chaque texte du .inx et des .py a sa traduction dans chaque catalogue."""
|
||||
import gettext
|
||||
import i18n
|
||||
|
||||
msgids = [msgid for msgid, _refs in i18n.extract()]
|
||||
assert "Finger-Joint Box" in msgids
|
||||
assert "Front" in msgids # noms des pieces, marques dans box_maker.py
|
||||
|
||||
for language in i18n.LANGUAGES:
|
||||
entries = i18n.read_po(i18n.po_path(language))
|
||||
missing = [m for m in msgids if not entries.get(m, ("", False))[0]]
|
||||
assert not missing, "{}.po incomplet : {}".format(language, missing)
|
||||
|
||||
catalog = gettext.translation(i18n.DOMAIN, i18n.LOCALE_DIR, [language])
|
||||
for msgid in msgids:
|
||||
assert catalog.gettext(msgid) == entries[msgid][0], \
|
||||
"{} : .mo a recompiler (python i18n.py)".format(language)
|
||||
|
||||
|
||||
def test_french_error_message_keeps_placeholders():
|
||||
"""La traduction du message d'erreur garde les memes champs de format."""
|
||||
import i18n
|
||||
|
||||
fields = re.compile(r"\{\w+(?::[^}]*)?\}")
|
||||
for msgid, (msgstr, _fuzzy) in i18n.read_po(i18n.po_path("fr")).items():
|
||||
assert sorted(fields.findall(msgid)) == sorted(fields.findall(msgstr)), msgid
|
||||
|
||||
|
||||
def test_po_roundtrip(tmp_path):
|
||||
import i18n
|
||||
|
||||
messages = [("Simple", ["a"]), ('Quote "x" and \\ back', ["a"]),
|
||||
("Two\nlines", ["a"]), ("Tab\tend\n", ["a"])]
|
||||
path = str(tmp_path / "xx.po")
|
||||
i18n.write_po(path, "fr", messages,
|
||||
{m: ("<" + m + ">", False) for m, _r in messages})
|
||||
entries = i18n.read_po(path)
|
||||
for msgid, _refs in messages:
|
||||
assert entries[msgid] == ("<" + msgid + ">", False)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Boite de dialogue
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
def test_inx_matches_arguments():
|
||||
"""Chaque <param> du .inx a son add_argument, et inversement."""
|
||||
root = ET.parse(INX).getroot()
|
||||
params = {elem.get("name") for elem in root.iter()
|
||||
if elem.tag.rsplit("}", 1)[-1] == "param"}
|
||||
with open(os.path.join(ROOT, "box_maker.py"), encoding="utf-8") as handle:
|
||||
arguments = set(re.findall(r'add_argument\("--(\w+)"', handle.read()))
|
||||
assert params == arguments
|
||||
|
||||
|
||||
def test_inx_images_exist():
|
||||
"""Les images de la boite de dialogue sont presentes (chemin relatif au .inx)."""
|
||||
root = ET.parse(INX).getroot()
|
||||
images = [elem.text.strip() for elem in root.iter()
|
||||
if elem.tag.rsplit("}", 1)[-1] == "image"]
|
||||
assert images
|
||||
for image in images:
|
||||
assert os.path.isfile(os.path.join(ROOT, image)), image
|
||||
|
||||
|
||||
def test_every_core_panel_has_a_translated_name():
|
||||
inkex = pytest.importorskip("inkex") # noqa: F841
|
||||
import box_maker
|
||||
|
||||
names = {p.name for p in box_core.box_panels(100, 80, 50, 3)}
|
||||
assert names == set(box_maker.panel_names())
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Bout en bout (avec inkex)
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
BLANK = ('<svg xmlns="http://www.w3.org/2000/svg" '
|
||||
'xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" '
|
||||
'width="300mm" height="300mm" viewBox="0 0 300 300">'
|
||||
'<g inkscape:groupmode="layer" id="layer1"/></svg>')
|
||||
|
||||
|
||||
def _run(tmp_path, *args):
|
||||
pytest.importorskip("inkex")
|
||||
import box_maker
|
||||
|
||||
source = tmp_path / "in.svg"
|
||||
source.write_text(BLANK, encoding="utf-8")
|
||||
output = tmp_path / "out.svg"
|
||||
box_maker.BoxMaker().run(list(args) + ["--output={}".format(output), str(source)])
|
||||
return output.read_text(encoding="utf-8") if output.exists() else ""
|
||||
|
||||
|
||||
def test_end_to_end_closed_box_with_labels(tmp_path):
|
||||
svg = _run(tmp_path, "--labels=true")
|
||||
assert svg.count("<path") == 6
|
||||
for name in ("Front", "Back", "Left", "Right", "Bottom", "Lid"):
|
||||
assert ">{}<".format(name) in svg
|
||||
|
||||
|
||||
def test_end_to_end_too_small_reports_error(tmp_path, capsys):
|
||||
_run(tmp_path, "--length=10")
|
||||
err = capsys.readouterr().err
|
||||
assert "length" in err and "at least 15 mm" in err
|
||||
Loading…
Reference in New Issue
Block a user