framework for translations (#55)

sets up all the plumbing to send strings to CrowdIn for translation and incorporate the results
pull/60/head
Lex Neva 2018-02-04 22:38:24 -05:00 zatwierdzone przez GitHub
rodzic 1dd76646cc
commit 72d52dc317
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
11 zmienionych plików z 461 dodań i 73 usunięć

1
.gitignore vendored
Wyświetl plik

@ -5,6 +5,7 @@
*.tar.gz *.tar.gz
dist/ dist/
build/ build/
locales/
# For development, I symlink into my clone, so I have to have a copy of libembroidery. # For development, I symlink into my clone, so I have to have a copy of libembroidery.
libembroidery.py libembroidery.py

Wyświetl plik

@ -5,10 +5,30 @@ VERSION:=$(TRAVIS_BRANCH)
OS:=$(shell uname) OS:=$(shell uname)
ARCH:=$(shell uname -m) ARCH:=$(shell uname -m)
dist: distclean dist: distclean locales
bin/build-dist $(EXTENSIONS) bin/build-dist $(EXTENSIONS)
cp *.inx dist cp *.inx dist
mv locales dist/bin
cd dist; tar zcf ../inkstitch-$(VERSION)-$(OS)-$(ARCH).tar.gz * cd dist; tar zcf ../inkstitch-$(VERSION)-$(OS)-$(ARCH).tar.gz *
distclean: distclean:
rm -rf build dist *.spec *.tar.gz rm -rf build dist *.spec *.tar.gz
messages.po: embroider*.py inkstitch.py
rm -f messages.po
xgettext embroider*.py inkstitch.py
.PHONY: locales
locales:
# message files will look like this:
# translations/messages-en_US.po
if ls translations/*.po > /dev/null 2>&1; then \
for po in translations/*.po; do \
lang=$${po%.po}; \
lang=$${lang#messages-}; \
mkdir -p locales/$$lang/LC_MESSAGES/; \
msgfmt $$po -o locales/$$lang/LC_MESSAGES/inkstitch.mo; \
done; \
else \
mkdir locales; \
fi

3
crowdin.yml 100644
Wyświetl plik

@ -0,0 +1,3 @@
files:
- source: messages.po
translation: /translations/messages_%locale_with_underscore%.po

Wyświetl plik

@ -35,20 +35,19 @@ import networkx
from pprint import pformat from pprint import pformat
import inkstitch import inkstitch
from inkstitch import cache, dbg, param, EmbroideryElement, get_nodes, SVG_POLYLINE_TAG, SVG_GROUP_TAG, PIXELS_PER_MM, get_viewbox_transform from inkstitch import _, cache, dbg, param, EmbroideryElement, get_nodes, SVG_POLYLINE_TAG, SVG_GROUP_TAG, PIXELS_PER_MM, get_viewbox_transform
class Fill(EmbroideryElement): class Fill(EmbroideryElement):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Fill, self).__init__(*args, **kwargs) super(Fill, self).__init__(*args, **kwargs)
@property @property
@param('auto_fill', 'Manually routed fill stitching', type='toggle', inverse=True, default=True) @param('auto_fill', _('Manually routed fill stitching'), type='toggle', inverse=True, default=True)
def auto_fill(self): def auto_fill(self):
return self.get_boolean_param('auto_fill', True) return self.get_boolean_param('auto_fill', True)
@property @property
@param('angle', 'Angle of lines of stitches', unit='deg', type='float', default=0) @param('angle', _('Angle of lines of stitches'), unit='deg', type='float', default=0)
@cache @cache
def angle(self): def angle(self):
return math.radians(self.get_float_param('angle', 0)) return math.radians(self.get_float_param('angle', 0))
@ -58,12 +57,12 @@ class Fill(EmbroideryElement):
return self.get_style("fill") return self.get_style("fill")
@property @property
@param('flip', 'Flip fill (start right-to-left)', type='boolean', default=False) @param('flip', _('Flip fill (start right-to-left)'), type='boolean', default=False)
def flip(self): def flip(self):
return self.get_boolean_param("flip", False) return self.get_boolean_param("flip", False)
@property @property
@param('row_spacing_mm', 'Spacing between rows', unit='mm', type='float', default=0.25) @param('row_spacing_mm', _('Spacing between rows'), unit='mm', type='float', default=0.25)
def row_spacing(self): def row_spacing(self):
return max(self.get_float_param("row_spacing_mm", 0.25), 0.01) return max(self.get_float_param("row_spacing_mm", 0.25), 0.01)
@ -72,12 +71,12 @@ class Fill(EmbroideryElement):
return self.get_float_param("end_row_spacing_mm") return self.get_float_param("end_row_spacing_mm")
@property @property
@param('max_stitch_length_mm', 'Maximum fill stitch length', unit='mm', type='float', default=3.0) @param('max_stitch_length_mm', _('Maximum fill stitch length'), unit='mm', type='float', default=3.0)
def max_stitch_length(self): def max_stitch_length(self):
return max(self.get_float_param("max_stitch_length_mm", 3.0), 0.01) return max(self.get_float_param("max_stitch_length_mm", 3.0), 0.01)
@property @property
@param('staggers', 'Stagger rows this many times before repeating', type='int', default=4) @param('staggers', _('Stagger rows this many times before repeating'), type='int', default=4)
def staggers(self): def staggers(self):
return self.get_int_param("staggers", 4) return self.get_int_param("staggers", 4)
@ -371,7 +370,7 @@ class MaxQueueLengthExceeded(Exception):
class AutoFill(Fill): class AutoFill(Fill):
@property @property
@param('auto_fill', 'Automatically routed fill stitching', type='toggle', default=True) @param('auto_fill', _('Automatically routed fill stitching'), type='toggle', default=True)
def auto_fill(self): def auto_fill(self):
return self.get_boolean_param('auto_fill', True) return self.get_boolean_param('auto_fill', True)
@ -390,17 +389,17 @@ class AutoFill(Fill):
return False return False
@property @property
@param('running_stitch_length_mm', 'Running stitch length (traversal between sections)', unit='mm', type='float', default=1.5) @param('running_stitch_length_mm', _('Running stitch length (traversal between sections)'), unit='mm', type='float', default=1.5)
def running_stitch_length(self): def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01) return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
@property @property
@param('fill_underlay', 'Underlay', type='toggle', group='AutoFill Underlay', default=False) @param('fill_underlay', _('Underlay'), type='toggle', group=_('AutoFill Underlay'), default=False)
def fill_underlay(self): def fill_underlay(self):
return self.get_boolean_param("fill_underlay", default=False) return self.get_boolean_param("fill_underlay", default=False)
@property @property
@param('fill_underlay_angle', 'Fill angle (default: fill angle + 90 deg)', unit='deg', group='AutoFill Underlay', type='float') @param('fill_underlay_angle', _('Fill angle (default: fill angle + 90 deg)'), unit='deg', group=_('AutoFill Underlay'), type='float')
@cache @cache
def fill_underlay_angle(self): def fill_underlay_angle(self):
underlay_angle = self.get_float_param("fill_underlay_angle") underlay_angle = self.get_float_param("fill_underlay_angle")
@ -411,13 +410,13 @@ class AutoFill(Fill):
return self.angle + math.pi / 2.0 return self.angle + math.pi / 2.0
@property @property
@param('fill_underlay_row_spacing_mm', 'Row spacing (default: 3x fill row spacing)', unit='mm', group='AutoFill Underlay', type='float') @param('fill_underlay_row_spacing_mm', _('Row spacing (default: 3x fill row spacing)'), unit='mm', group=_('AutoFill Underlay'), type='float')
@cache @cache
def fill_underlay_row_spacing(self): def fill_underlay_row_spacing(self):
return self.get_float_param("fill_underlay_row_spacing_mm") or self.row_spacing * 3 return self.get_float_param("fill_underlay_row_spacing_mm") or self.row_spacing * 3
@property @property
@param('fill_underlay_max_stitch_length_mm', 'Max stitch length', unit='mm', group='AutoFill Underlay', type='float') @param('fill_underlay_max_stitch_length_mm', _('Max stitch length'), unit='mm', group=_('AutoFill Underlay'), type='float')
@cache @cache
def fill_underlay_max_stitch_length(self): def fill_underlay_max_stitch_length(self):
return self.get_float_param("fill_underlay_max_stitch_length_mm") or self.max_stitch_length return self.get_float_param("fill_underlay_max_stitch_length_mm") or self.max_stitch_length
@ -538,7 +537,7 @@ class AutoFill(Fill):
if not networkx.is_eulerian(graph): if not networkx.is_eulerian(graph):
raise Exception("something went wrong: graph is not eulerian") raise Exception(_("Unable to autofill. This most often happens because your shape is made up of multiple sections that aren't connected."))
return graph return graph
@ -713,7 +712,7 @@ class AutoFill(Fill):
result = self.find_loop(graph, nodes_visited) result = self.find_loop(graph, nodes_visited)
if not result: if not result:
print >> sys.stderr, "Unexpected error filling region. Please send your SVG to lexelby@github." print >> sys.stderr, _("Unexpected error while generating fill stitches. Please send your SVG file to lexelby@github.")
break break
loop, segments = result loop, segments = result
@ -882,7 +881,7 @@ class AutoFill(Fill):
class Stroke(EmbroideryElement): class Stroke(EmbroideryElement):
@property @property
@param('satin_column', 'Satin along paths', type='toggle', inverse=True) @param('satin_column', _('Satin stitch along paths'), type='toggle', inverse=True)
def satin_column(self): def satin_column(self):
return self.get_boolean_param("satin_column") return self.get_boolean_param("satin_column")
@ -905,18 +904,18 @@ class Stroke(EmbroideryElement):
return self.get_style("stroke-dasharray") is not None return self.get_style("stroke-dasharray") is not None
@property @property
@param('running_stitch_length_mm', 'Running stitch length', unit='mm', type='float', default=1.5) @param('running_stitch_length_mm', _('Running stitch length'), unit='mm', type='float', default=1.5)
def running_stitch_length(self): def running_stitch_length(self):
return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01) return max(self.get_float_param("running_stitch_length_mm", 1.5), 0.01)
@property @property
@param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float', default=0.4) @param('zigzag_spacing_mm', _('Zig-zag spacing (peak-to-peak)'), unit='mm', type='float', default=0.4)
@cache @cache
def zigzag_spacing(self): def zigzag_spacing(self):
return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01) return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01)
@property @property
@param('repeats', 'Repeats', type='int', default="1") @param('repeats', _('Repeats'), type='int', default="1")
def repeats(self): def repeats(self):
return self.get_int_param("repeats", 1) return self.get_int_param("repeats", 1)
@ -997,7 +996,7 @@ class SatinColumn(EmbroideryElement):
super(SatinColumn, self).__init__(*args, **kwargs) super(SatinColumn, self).__init__(*args, **kwargs)
@property @property
@param('satin_column', 'Custom satin column', type='toggle') @param('satin_column', _('Custom satin column'), type='toggle')
def satin_column(self): def satin_column(self):
return self.get_boolean_param("satin_column") return self.get_boolean_param("satin_column")
@ -1006,13 +1005,13 @@ class SatinColumn(EmbroideryElement):
return self.get_style("stroke") return self.get_style("stroke")
@property @property
@param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float', default=0.4) @param('zigzag_spacing_mm', _('Zig-zag spacing (peak-to-peak)'), unit='mm', type='float', default=0.4)
def zigzag_spacing(self): def zigzag_spacing(self):
# peak-to-peak distance between zigzags # peak-to-peak distance between zigzags
return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01) return max(self.get_float_param("zigzag_spacing_mm", 0.4), 0.01)
@property @property
@param('pull_compensation_mm', 'Pull compensation', unit='mm', type='float') @param('pull_compensation_mm', _('Pull compensation'), unit='mm', type='float')
def pull_compensation(self): def pull_compensation(self):
# In satin stitch, the stitches have a tendency to pull together and # In satin stitch, the stitches have a tendency to pull together and
# narrow the entire column. We can compensate for this by stitching # narrow the entire column. We can compensate for this by stitching
@ -1020,47 +1019,47 @@ class SatinColumn(EmbroideryElement):
return self.get_float_param("pull_compensation_mm", 0) return self.get_float_param("pull_compensation_mm", 0)
@property @property
@param('contour_underlay', 'Contour underlay', type='toggle', group='Contour Underlay') @param('contour_underlay', _('Contour underlay'), type='toggle', group=_('Contour Underlay'))
def contour_underlay(self): def contour_underlay(self):
# "Contour underlay" is stitching just inside the rectangular shape # "Contour underlay" is stitching just inside the rectangular shape
# of the satin column; that is, up one side and down the other. # of the satin column; that is, up one side and down the other.
return self.get_boolean_param("contour_underlay") return self.get_boolean_param("contour_underlay")
@property @property
@param('contour_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Contour Underlay', type='float', default=1.5) @param('contour_underlay_stitch_length_mm', _('Stitch length'), unit='mm', group=_('Contour Underlay'), type='float', default=1.5)
def contour_underlay_stitch_length(self): def contour_underlay_stitch_length(self):
return max(self.get_float_param("contour_underlay_stitch_length_mm", 1.5), 0.01) return max(self.get_float_param("contour_underlay_stitch_length_mm", 1.5), 0.01)
@property @property
@param('contour_underlay_inset_mm', 'Contour underlay inset amount', unit='mm', group='Contour Underlay', type='float', default=0.4) @param('contour_underlay_inset_mm', _('Contour underlay inset amount'), unit='mm', group=_('Contour Underlay'), type='float', default=0.4)
def contour_underlay_inset(self): def contour_underlay_inset(self):
# how far inside the edge of the column to stitch the underlay # how far inside the edge of the column to stitch the underlay
return self.get_float_param("contour_underlay_inset_mm", 0.4) return self.get_float_param("contour_underlay_inset_mm", 0.4)
@property @property
@param('center_walk_underlay', 'Center-walk underlay', type='toggle', group='Center-Walk Underlay') @param('center_walk_underlay', _('Center-walk underlay'), type='toggle', group=_('Center-Walk Underlay'))
def center_walk_underlay(self): def center_walk_underlay(self):
# "Center walk underlay" is stitching down and back in the centerline # "Center walk underlay" is stitching down and back in the centerline
# between the two sides of the satin column. # between the two sides of the satin column.
return self.get_boolean_param("center_walk_underlay") return self.get_boolean_param("center_walk_underlay")
@property @property
@param('center_walk_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Center-Walk Underlay', type='float', default=1.5) @param('center_walk_underlay_stitch_length_mm', _('Stitch length'), unit='mm', group=_('Center-Walk Underlay'), type='float', default=1.5)
def center_walk_underlay_stitch_length(self): def center_walk_underlay_stitch_length(self):
return max(self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5), 0.01) return max(self.get_float_param("center_walk_underlay_stitch_length_mm", 1.5), 0.01)
@property @property
@param('zigzag_underlay', 'Zig-zag underlay', type='toggle', group='Zig-zag Underlay') @param('zigzag_underlay', _('Zig-zag underlay'), type='toggle', group=_('Zig-zag Underlay'))
def zigzag_underlay(self): def zigzag_underlay(self):
return self.get_boolean_param("zigzag_underlay") return self.get_boolean_param("zigzag_underlay")
@property @property
@param('zigzag_underlay_spacing_mm', 'Zig-Zag spacing (peak-to-peak)', unit='mm', group='Zig-zag Underlay', type='float', default=3) @param('zigzag_underlay_spacing_mm', _('Zig-Zag spacing (peak-to-peak)'), unit='mm', group=_('Zig-zag Underlay'), type='float', default=3)
def zigzag_underlay_spacing(self): def zigzag_underlay_spacing(self):
return max(self.get_float_param("zigzag_underlay_spacing_mm", 3), 0.01) return max(self.get_float_param("zigzag_underlay_spacing_mm", 3), 0.01)
@property @property
@param('zigzag_underlay_inset_mm', 'Inset amount (default: half of contour underlay inset)', unit='mm', group='Zig-zag Underlay', type='float') @param('zigzag_underlay_inset_mm', _('Inset amount (default: half of contour underlay inset)'), unit='mm', group=_('Zig-zag Underlay'), type='float')
def zigzag_underlay_inset(self): def zigzag_underlay_inset(self):
# how far in from the edge of the satin the points in the zigzags # how far in from the edge of the satin the points in the zigzags
# should be # should be
@ -1106,14 +1105,16 @@ class SatinColumn(EmbroideryElement):
for rail in rails: for rail in rails:
if not rail.is_simple: if not rail.is_simple:
self.fatal("One or more rails crosses itself, and this is not allowed. Please split into multiple satin columns.") self.fatal(_("One or more rails crosses itself, and this is not allowed. Please split into multiple satin columns."))
# handle null intersections here? # handle null intersections here?
linestrings = shapely.ops.split(rail, rungs) linestrings = shapely.ops.split(rail, rungs)
print >> dbg, "rails and rungs", [str(rail) for rail in rails], [str(rung) for rung in rungs]
if len(linestrings.geoms) < len(rungs.geoms) + 1: if len(linestrings.geoms) < len(rungs.geoms) + 1:
print >> dbg, [str(rail) for rail in rails], [str(rung) for rung in rungs] self.fatal(_("satin column: One or more of the rungs doesn't intersect both rails.") + " " + _("Each rail should intersect both rungs once."))
self.fatal("Expected %d linestrings, got %d" % (len(rungs.geoms) + 1, len(linestrings.geoms))) elif len(linestrings.geoms) > len(rungs.geoms) + 1:
self.fatal(_("satin column: One or more of the rungs intersects the rails more than once.") + " " + _("Each rail should intersect both rungs once."))
paths = [[inkstitch.Point(*coord) for coord in ls.coords] for ls in linestrings.geoms] paths = [[inkstitch.Point(*coord) for coord in ls.coords] for ls in linestrings.geoms]
result.append(paths) result.append(paths)
@ -1154,11 +1155,12 @@ class SatinColumn(EmbroideryElement):
node_id = self.node.get("id") node_id = self.node.get("id")
if self.get_style("fill") is not None: if self.get_style("fill") is not None:
self.fatal("satin column: object %s has a fill (but should not)" % node_id) self.fatal(_("satin column: object %s has a fill (but should not)") % node_id)
if len(self.csp) == 2: if len(self.csp) == 2:
if len(self.csp[0]) != len(self.csp[1]): if len(self.csp[0]) != len(self.csp[1]):
self.fatal("satin column: object %s has two paths with an unequal number of points (%s and %s)" % (node_id, len(self.csp[0]), len(self.csp[1]))) self.fatal(_("satin column: object %(id)s has two paths with an unequal number of points (%(length1)d and %(length2)d)") % \
dict(id=node_id, length1=len(self.csp[0]), length2=len(self.csp[1])))
def offset_points(self, pos1, pos2, offset_px): def offset_points(self, pos1, pos2, offset_px):
# Expand or contract two points about their midpoint. This is # Expand or contract two points about their midpoint. This is
@ -1690,7 +1692,7 @@ class Embroider(inkex.Effect):
action="store", type="int", action="store", type="int",
dest="max_backups", default=5, dest="max_backups", default=5,
help="Max number of backups of output files to keep.") help="Max number of backups of output files to keep.")
self.OptionParser.usage += "\n\nSeeing a 'no such option' message? Please restart Inkscape to fix." self.OptionParser.usage += _("\n\nSeeing a 'no such option' message? Please restart Inkscape to fix.")
def get_output_path(self): def get_output_path(self):
if self.options.output_file: if self.options.output_file:
@ -1738,10 +1740,10 @@ class Embroider(inkex.Effect):
if not self.elements: if not self.elements:
if self.selected: if self.selected:
inkex.errormsg("No embroiderable paths selected.") inkex.errormsg(_("No embroiderable paths selected."))
else: else:
inkex.errormsg("No embroiderable paths found in document.") inkex.errormsg(_("No embroiderable paths found in document."))
inkex.errormsg("Tip: use Path -> Object to Path to convert non-paths before embroidering.") inkex.errormsg(_("Tip: use Path -> Object to Path to convert non-paths before embroidering."))
return return
if self.options.hide_layers: if self.options.hide_layers:
@ -1753,7 +1755,7 @@ class Embroider(inkex.Effect):
new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {}) new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {})
new_layer.set('id', self.uniqueId("embroidery")) new_layer.set('id', self.uniqueId("embroidery"))
new_layer.set(inkex.addNS('label', 'inkscape'), 'Embroidery') new_layer.set(inkex.addNS('label', 'inkscape'), _('Embroidery'))
new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
emit_inkscape(new_layer, stitches) emit_inkscape(new_layer, stitches)

Wyświetl plik

@ -13,12 +13,14 @@ import wx
from wx.lib.scrolledpanel import ScrolledPanel from wx.lib.scrolledpanel import ScrolledPanel
from collections import defaultdict from collections import defaultdict
import inkex import inkex
from inkstitch import Param, EmbroideryElement, get_nodes import inkstitch
from inkstitch import _, Param, EmbroideryElement, get_nodes
from embroider import Fill, AutoFill, Stroke, SatinColumn from embroider import Fill, AutoFill, Stroke, SatinColumn
from functools import partial from functools import partial
from itertools import groupby from itertools import groupby
from embroider_simulate import EmbroiderySimulator from embroider_simulate import EmbroiderySimulator
def presets_path(): def presets_path():
try: try:
import appdirs import appdirs
@ -237,17 +239,22 @@ class ParamsTab(ScrolledPanel):
preset[name] = input.GetValue() preset[name] = input.GetValue()
def update_description(self): def update_description(self):
description = "These settings will be applied to %d object%s." % \ if len(self.nodes) == 1:
(len(self.nodes), "s" if len(self.nodes) != 1 else "") description = _("These settings will be applied to 1 object.")
else:
description = _("These settings will be applied to %d objects.") % len(self.nodes)
if any(len(param.values) > 1 for param in self.params): if any(len(param.values) > 1 for param in self.params):
description += "\nSome settings had different values across objects. Select a value from the dropdown or enter a new one." description += "\n" + _("Some settings had different values across objects. Select a value from the dropdown or enter a new one.")
if self.dependent_tabs: if self.dependent_tabs:
description += "\n • Disabling this tab will disable the following %d tabs." % len(self.dependent_tabs) if len(self.dependent_tabs) == 1:
description += "\n" + _("Disabling this tab will disable the following %d tabs.") % len(self.dependent_tabs)
else:
description += "\n" + _("Disabling this tab will disable the following tab.")
if self.paired_tab: if self.paired_tab:
description += "\n • Enabling this tab will disable %s and vice-versa." % self.paired_tab.name description += "\n" + _("Enabling this tab will disable %s and vice-versa.") % self.paired_tab.name
self.description_text = description self.description_text = description
@ -277,7 +284,7 @@ class ParamsTab(ScrolledPanel):
# just to add space around the settings # just to add space around the settings
box = wx.BoxSizer(wx.VERTICAL) box = wx.BoxSizer(wx.VERTICAL)
summary_box = wx.StaticBox(self, wx.ID_ANY, label="Inkscape objects") summary_box = wx.StaticBox(self, wx.ID_ANY, label=_("Inkscape objects"))
sizer = wx.StaticBoxSizer(summary_box, wx.HORIZONTAL) sizer = wx.StaticBoxSizer(summary_box, wx.HORIZONTAL)
# sizer = wx.BoxSizer(wx.HORIZONTAL) # sizer = wx.BoxSizer(wx.HORIZONTAL)
self.description = wx.StaticText(self, style=wx.TE_WORDWRAP) self.description = wx.StaticText(self, style=wx.TE_WORDWRAP)
@ -335,7 +342,7 @@ class SettingsFrame(wx.Frame):
self.tabs_factory = kwargs.pop('tabs_factory', []) self.tabs_factory = kwargs.pop('tabs_factory', [])
self.cancel_hook = kwargs.pop('on_cancel', None) self.cancel_hook = kwargs.pop('on_cancel', None)
wx.Frame.__init__(self, None, wx.ID_ANY, wx.Frame.__init__(self, None, wx.ID_ANY,
"Embroidery Params" _("Embroidery Params")
) )
self.notebook = wx.Notebook(self, wx.ID_ANY) self.notebook = wx.Notebook(self, wx.ID_ANY)
self.tabs = self.tabs_factory(self.notebook) self.tabs = self.tabs_factory(self.notebook)
@ -349,31 +356,31 @@ class SettingsFrame(wx.Frame):
wx.CallLater(1000, self.update_simulator) wx.CallLater(1000, self.update_simulator)
self.presets_box = wx.StaticBox(self, wx.ID_ANY, label="Presets") self.presets_box = wx.StaticBox(self, wx.ID_ANY, label=_("Presets"))
self.preset_chooser = wx.ComboBox(self, wx.ID_ANY) self.preset_chooser = wx.ComboBox(self, wx.ID_ANY)
self.update_preset_list() self.update_preset_list()
self.load_preset_button = wx.Button(self, wx.ID_ANY, "Load") self.load_preset_button = wx.Button(self, wx.ID_ANY, _("Load"))
self.load_preset_button.Bind(wx.EVT_BUTTON, self.load_preset) self.load_preset_button.Bind(wx.EVT_BUTTON, self.load_preset)
self.add_preset_button = wx.Button(self, wx.ID_ANY, "Add") self.add_preset_button = wx.Button(self, wx.ID_ANY, _("Add"))
self.add_preset_button.Bind(wx.EVT_BUTTON, self.add_preset) self.add_preset_button.Bind(wx.EVT_BUTTON, self.add_preset)
self.overwrite_preset_button = wx.Button(self, wx.ID_ANY, "Overwrite") self.overwrite_preset_button = wx.Button(self, wx.ID_ANY, _("Overwrite"))
self.overwrite_preset_button.Bind(wx.EVT_BUTTON, self.overwrite_preset) self.overwrite_preset_button.Bind(wx.EVT_BUTTON, self.overwrite_preset)
self.delete_preset_button = wx.Button(self, wx.ID_ANY, "Delete") self.delete_preset_button = wx.Button(self, wx.ID_ANY, _("Delete"))
self.delete_preset_button.Bind(wx.EVT_BUTTON, self.delete_preset) self.delete_preset_button.Bind(wx.EVT_BUTTON, self.delete_preset)
self.cancel_button = wx.Button(self, wx.ID_ANY, "Cancel") self.cancel_button = wx.Button(self, wx.ID_ANY, _("Cancel"))
self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel) self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel)
self.Bind(wx.EVT_CLOSE, self.cancel) self.Bind(wx.EVT_CLOSE, self.cancel)
self.use_last_button = wx.Button(self, wx.ID_ANY, "Use Last Settings") self.use_last_button = wx.Button(self, wx.ID_ANY, _("Use Last Settings"))
self.use_last_button.Bind(wx.EVT_BUTTON, self.use_last) self.use_last_button.Bind(wx.EVT_BUTTON, self.use_last)
self.apply_button = wx.Button(self, wx.ID_ANY, "Apply and Quit") self.apply_button = wx.Button(self, wx.ID_ANY, _("Apply and Quit"))
self.apply_button.Bind(wx.EVT_BUTTON, self.apply) self.apply_button.Bind(wx.EVT_BUTTON, self.apply)
self.__set_properties() self.__set_properties()
@ -418,7 +425,7 @@ class SettingsFrame(wx.Frame):
max_height = screen_rect.GetHeight() max_height = screen_rect.GetHeight()
try: try:
self.simulate_window = EmbroiderySimulator(None, -1, "Embroidery Simulator", self.simulate_window = EmbroiderySimulator(None, -1, _("Preview"),
simulator_pos, simulator_pos,
size=(300, 300), size=(300, 300),
patches=patches, patches=patches,
@ -432,11 +439,11 @@ class SettingsFrame(wx.Frame):
try: try:
# a window may have been created, so we need to destroy it # a window may have been created, so we need to destroy it
# or the app will never exit # or the app will never exit
wx.Window.FindWindowByName("Embroidery Simulator").Destroy() wx.Window.FindWindowByName("Preview").Destroy()
except: except:
pass pass
info_dialog(self, error, "Internal Error") info_dialog(self, error, _("Internal Error"))
self.simulate_window.Show() self.simulate_window.Show()
wx.CallLater(10, self.Raise) wx.CallLater(10, self.Raise)
@ -489,13 +496,13 @@ class SettingsFrame(wx.Frame):
if preset_name: if preset_name:
return preset_name return preset_name
else: else:
info_dialog(self, "Please enter or select a preset name first.", caption='Preset') info_dialog(self, _("Please enter or select a preset name first."), caption=_('Preset'))
return return
def check_and_load_preset(self, preset_name): def check_and_load_preset(self, preset_name):
preset = load_preset(preset_name) preset = load_preset(preset_name)
if not preset: if not preset:
info_dialog(self, 'Preset "%s" not found.' % preset_name, caption='Preset') info_dialog(self, _('Preset "%s" not found.') % preset_name, caption=_('Preset'))
return preset return preset
@ -523,7 +530,7 @@ class SettingsFrame(wx.Frame):
return return
if not overwrite and load_preset(preset_name): if not overwrite and load_preset(preset_name):
info_dialog(self, 'Preset "%s" already exists. Please use another name or press "Overwrite"' % preset_name, caption='Preset') info_dialog(self, _('Preset "%s" already exists. Please use another name or press "Overwrite"') % preset_name, caption=_('Preset'))
save_preset(preset_name, self.get_preset_data()) save_preset(preset_name, self.get_preset_data())
self.update_preset_list() self.update_preset_list()
@ -596,7 +603,6 @@ class SettingsFrame(wx.Frame):
def __set_properties(self): def __set_properties(self):
# begin wxGlade: MyFrame.__set_properties # begin wxGlade: MyFrame.__set_properties
self.SetTitle("Embroidery Parameters")
self.notebook.SetMinSize((800, 600)) self.notebook.SetMinSize((800, 600))
self.preset_chooser.SetSelection(-1) self.preset_chooser.SetSelection(-1)
# end wxGlade # end wxGlade

Wyświetl plik

@ -6,8 +6,10 @@ import inkex
import simplestyle import simplestyle
import colorsys import colorsys
import inkstitch
from inkstitch import PIXELS_PER_MM from inkstitch import PIXELS_PER_MM
from embroider import patches_to_stitches, get_elements, elements_to_patches from embroider import _, patches_to_stitches, get_elements, elements_to_patches
class EmbroiderySimulator(wx.Frame): class EmbroiderySimulator(wx.Frame):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -298,7 +300,7 @@ class SimulateEffect(inkex.Effect):
def effect(self): def effect(self):
patches = elements_to_patches(get_elements(self)) patches = elements_to_patches(get_elements(self))
app = wx.App() app = wx.App()
frame = EmbroiderySimulator(None, -1, "Embroidery Simulation", wx.DefaultPosition, size=(1000, 1000), patches=patches) frame = EmbroiderySimulator(None, -1, _("Embroidery Simulation"), wx.DefaultPosition, size=(1000, 1000), patches=patches)
app.SetTopWindow(frame) app.SetTopWindow(frame)
frame.Show() frame.Show()
wx.CallAfter(frame.go) wx.CallAfter(frame.go)

Wyświetl plik

@ -0,0 +1 @@
Files in this directory are meant to be symlinked or copied into your local clone's .git/hooks directory.

Wyświetl plik

@ -0,0 +1,4 @@
#!/bin/bash
make messages.po | grep -v 'is up to date\.$'
git add messages.po

Wyświetl plik

@ -1,7 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# http://www.achatina.de/sewing/main/TECHNICL.HTM # http://www.achatina.de/sewing/main/TECHNICL.HTM
import os
import sys import sys
import gettext
from copy import deepcopy from copy import deepcopy
import math import math
import libembroidery import libembroidery
@ -31,10 +33,28 @@ EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
dbg = open("/tmp/embroider-debug.txt", "w") dbg = open("/tmp/embroider-debug.txt", "w")
_ = lambda message: message
# simplify use of lru_cache decorator # simplify use of lru_cache decorator
def cache(*args, **kwargs): def cache(*args, **kwargs):
return lru_cache(maxsize=None)(*args, **kwargs) return lru_cache(maxsize=None)(*args, **kwargs)
def localize():
if getattr(sys, 'frozen', False):
# we are in a pyinstaller installation
locale_dir = sys._MEIPASS
else:
locale_dir = os.path.dirname(__file__)
locale_dir = os.path.join(locale_dir, 'locales')
translation = gettext.translation("inkstitch", locale_dir, fallback=True)
global _
_ = translation.gettext
localize()
# cribbed from inkscape-silhouette # cribbed from inkscape-silhouette
def parse_length_with_units( str ): def parse_length_with_units( str ):
@ -71,7 +91,7 @@ def parse_length_with_units( str ):
try: try:
v = float( s ) v = float( s )
except: except:
raise ValueError("parseLengthWithUnits: unknown unit %s" % s) raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)
return v, u return v, u
@ -95,7 +115,7 @@ def convert_length(length):
# open an old document, inkscape will add a viewbox for you. # open an old document, inkscape will add a viewbox for you.
return value * 96 return value * 96
raise ValueError("Unknown unit: %s" % units) raise ValueError(_("Unknown unit: %s") % units)
@cache @cache
@ -298,8 +318,8 @@ class EmbroideryElement(object):
@property @property
@param('trim_after', @param('trim_after',
'TRIM after', _('TRIM after'),
tooltip='Trim thread after this object (for supported machines and file formats)', tooltip=_('Trim thread after this object (for supported machines and file formats)'),
type='boolean', type='boolean',
default=False, default=False,
sort_index=1000) sort_index=1000)
@ -308,8 +328,8 @@ class EmbroideryElement(object):
@property @property
@param('stop_after', @param('stop_after',
'STOP after', _('STOP after'),
tooltip='Add STOP instruction after this object (for supported machines and file formats)', tooltip=_('Add STOP instruction after this object (for supported machines and file formats)'),
type='boolean', type='boolean',
default=False, default=False,
sort_index=1000) sort_index=1000)

329
messages.po 100644
Wyświetl plik

@ -0,0 +1,329 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-04 16:24-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: embroider.py:45
msgid "Manually routed fill stitching"
msgstr ""
#: embroider.py:50
msgid "Angle of lines of stitches"
msgstr ""
#: embroider.py:60
msgid "Flip fill (start right-to-left)"
msgstr ""
#: embroider.py:65
msgid "Spacing between rows"
msgstr ""
#: embroider.py:74
msgid "Maximum fill stitch length"
msgstr ""
#: embroider.py:79
msgid "Stagger rows this many times before repeating"
msgstr ""
#: embroider.py:373
msgid "Automatically routed fill stitching"
msgstr ""
#: embroider.py:392
msgid "Running stitch length (traversal between sections)"
msgstr ""
#: embroider.py:397
msgid "Underlay"
msgstr ""
#: embroider.py:397 embroider.py:402 embroider.py:413 embroider.py:419
msgid "AutoFill Underlay"
msgstr ""
#: embroider.py:402
msgid "Fill angle (default: fill angle + 90 deg)"
msgstr ""
#: embroider.py:413
msgid "Row spacing (default: 3x fill row spacing)"
msgstr ""
#: embroider.py:419
msgid "Max stitch length"
msgstr ""
#: embroider.py:540
msgid ""
"Unable to autofill. This most often happens because your shape is made up "
"of multiple sections that aren't connected."
msgstr ""
#: embroider.py:715
msgid ""
"Unexpected error while generating fill stitches. Please send your SVG file "
"to lexelby@github."
msgstr ""
#: embroider.py:884
msgid "Satin stitch along paths"
msgstr ""
#: embroider.py:907
msgid "Running stitch length"
msgstr ""
#: embroider.py:912 embroider.py:1008
msgid "Zig-zag spacing (peak-to-peak)"
msgstr ""
#: embroider.py:918
msgid "Repeats"
msgstr ""
#: embroider.py:999
msgid "Custom satin column"
msgstr ""
#: embroider.py:1014
msgid "Pull compensation"
msgstr ""
#: embroider.py:1022
msgid "Contour underlay"
msgstr ""
#: embroider.py:1022 embroider.py:1029 embroider.py:1034
msgid "Contour Underlay"
msgstr ""
#: embroider.py:1029 embroider.py:1047
msgid "Stitch length"
msgstr ""
#: embroider.py:1034
msgid "Contour underlay inset amount"
msgstr ""
#: embroider.py:1040
msgid "Center-walk underlay"
msgstr ""
#: embroider.py:1040 embroider.py:1047
msgid "Center-Walk Underlay"
msgstr ""
#: embroider.py:1052
msgid "Zig-zag underlay"
msgstr ""
#: embroider.py:1052 embroider.py:1057 embroider.py:1062
msgid "Zig-zag Underlay"
msgstr ""
#: embroider.py:1057
msgid "Zig-Zag spacing (peak-to-peak)"
msgstr ""
#: embroider.py:1062
msgid "Inset amount (default: half of contour underlay inset)"
msgstr ""
#: embroider.py:1108
msgid ""
"One or more rails crosses itself, and this is not allowed. Please split "
"into multiple satin columns."
msgstr ""
#: embroider.py:1115
msgid "satin column: One or more of the rungs doesn't intersect both rails."
msgstr ""
#: embroider.py:1115 embroider.py:1117
msgid "Each rail should intersect both rungs once."
msgstr ""
#: embroider.py:1117
msgid ""
"satin column: One or more of the rungs intersects the rails more than once."
msgstr ""
#: embroider.py:1158
#, python-format
msgid "satin column: object %s has a fill (but should not)"
msgstr ""
#: embroider.py:1162
#, python-format
msgid ""
"satin column: object %(id)s has two paths with an unequal number of points "
"(%(length1)d and %(length2)d)"
msgstr ""
#: embroider.py:1695
msgid ""
"\n"
"\n"
"Seeing a 'no such option' message? Please restart Inkscape to fix."
msgstr ""
#: embroider.py:1743
msgid "No embroiderable paths selected."
msgstr ""
#: embroider.py:1745
msgid "No embroiderable paths found in document."
msgstr ""
#: embroider.py:1746
msgid ""
"Tip: use Path -> Object to Path to convert non-paths before embroidering."
msgstr ""
#: embroider.py:1758
msgid "Embroidery"
msgstr ""
#: embroider_params.py:243
msgid "These settings will be applied to 1 object."
msgstr ""
#: embroider_params.py:245
#, python-format
msgid "These settings will be applied to %d objects."
msgstr ""
#: embroider_params.py:248
msgid ""
"Some settings had different values across objects. Select a value from the "
"dropdown or enter a new one."
msgstr ""
#: embroider_params.py:252
#, python-format
msgid "Disabling this tab will disable the following %d tabs."
msgstr ""
#: embroider_params.py:254
msgid "Disabling this tab will disable the following tab."
msgstr ""
#: embroider_params.py:257
#, python-format
msgid "Enabling this tab will disable %s and vice-versa."
msgstr ""
#: embroider_params.py:287
msgid "Inkscape objects"
msgstr ""
#: embroider_params.py:345
msgid "Embroidery Params"
msgstr ""
#: embroider_params.py:359
msgid "Presets"
msgstr ""
#: embroider_params.py:364
msgid "Load"
msgstr ""
#: embroider_params.py:367
msgid "Add"
msgstr ""
#: embroider_params.py:370
msgid "Overwrite"
msgstr ""
#: embroider_params.py:373
msgid "Delete"
msgstr ""
#: embroider_params.py:376
msgid "Cancel"
msgstr ""
#: embroider_params.py:380
msgid "Use Last Settings"
msgstr ""
#: embroider_params.py:383
msgid "Apply and Quit"
msgstr ""
#: embroider_params.py:428
msgid "Preview"
msgstr ""
#: embroider_params.py:446
msgid "Internal Error"
msgstr ""
#: embroider_params.py:499
msgid "Please enter or select a preset name first."
msgstr ""
#: embroider_params.py:499 embroider_params.py:505 embroider_params.py:533
msgid "Preset"
msgstr ""
#: embroider_params.py:505
#, python-format
msgid "Preset \"%s\" not found."
msgstr ""
#: embroider_params.py:533
#, python-format
msgid ""
"Preset \"%s\" already exists. Please use another name or press \"Overwrite\""
msgstr ""
#: embroider_simulate.py:303
msgid "Embroidery Simulation"
msgstr ""
#: inkstitch.py:94
#, python-format
msgid "parseLengthWithUnits: unknown unit %s"
msgstr ""
#: inkstitch.py:118
#, python-format
msgid "Unknown unit: %s"
msgstr ""
#: inkstitch.py:321
msgid "TRIM after"
msgstr ""
#: inkstitch.py:322
msgid "Trim thread after this object (for supported machines and file formats)"
msgstr ""
#: inkstitch.py:331
msgid "STOP after"
msgstr ""
#: inkstitch.py:332
msgid ""
"Add STOP instruction after this object (for supported machines and file "
"formats)"
msgstr ""

Wyświetl plik