Add whitespace font to highlight different types of whitespace.

Julien LepillerSat Nov 14 02:36:48+0100 2020

7acd08b

Add whitespace font to highlight different types of whitespace.

.gitignore

77
*~
88
__pycache__
99
offlate.spec
10+
whitespace.ttf

Makefile

3434
update-data:
3535
	python3 extractdata.py
3636
37+
fonts: offlate/ui/data/whitespace.ttf
38+
39+
offlate/ui/data/whitespace.ttf: offlate/ui/data/whitespace.sfd
40+
	fontforge -lang=ff -script fontforge_whitespace
41+
3742
LANGS=en fr
3843
3944
update-langs:

fontforge_whitespace unknown status 1

1+
Open("offlate/ui/data/whitespace.sfd")
2+
Generate("offlate/ui/data/whitespace.ttf")

guix.manifest

11
(specifications->manifest
22
 '("arc-icon-theme"; for displaying standard icons
3+
   "fontforge"; for generating the whitespace ttf font
34
   "make"
45
   "python"
56
   "python-android-stringslib"

guix.scm

1616
  (gnu packages time)
1717
  (gnu packages version-control))
1818
19-
(define-public python-pyenchant
19+
(define-public offlate
2020
  (package
21-
    (name "python-pyenchant")
22-
    (version "2.0.0")
23-
    (source (origin
24-
              (method url-fetch)
25-
              (uri (pypi-uri "pyenchant" version))
26-
              (sha256
27-
               (base32
28-
                "1872ckgdip8nj9rnh167m0gsj5754qfg2hjxzsl1s06f5akwscgw"))))
29-
    (build-system python-build-system)
30-
    (arguments
31-
     `(#:tests? #f; FIXME: Dictionary for language 'en_US' could not be found
32-
       #:phases
33-
       (modify-phases %standard-phases
34-
         (add-before 'build 'setlib
35-
           (lambda* (#:key inputs #:allow-other-keys)
36-
             (substitute* "enchant/_enchant.py"
37-
               (("/opt/local/lib/libenchant.dylib\"")
38-
                (string-append "/opt/local/lib/libenchant.dylib\"\n"
39-
                               "    yield \"" (assoc-ref inputs "enchant")
40-
                               "/lib/libenchant-2.so\""))))))))
41-
             ;(setenv "PYENCHANT_LIBRARY_PATH"
42-
             ;        (string-append (assoc-ref inputs "enchant") "/lib/libenchant.so")))))))
43-
    (propagated-inputs
44-
     `(("enchant" ,enchant)
45-
       ("hunspell" ,hunspell)))
46-
    (native-inputs
47-
     `(("hunspell-dict-en-us" ,hunspell-dict-en-us)))
48-
    (home-page "")
49-
    (synopsis "")
50-
    (description "")
51-
    (license license:lgpl2.1+)))
52-
53-
(define-public python-check-manifest
54-
  (package
55-
    (name "python-check-manifest")
56-
    (version "0.37")
57-
    (source
58-
      (origin
59-
        (method url-fetch)
60-
        (uri (pypi-uri "check-manifest" version))
61-
        (sha256
62-
          (base32
63-
            "0lk45ifdv2cpkl6ayfyix7jwmnxa1rha7xvb0ih5999k115wzqs4"))))
64-
    (build-system python-build-system)
65-
    (native-inputs
66-
      `(("python-mock" ,python-mock)
67-
        ("git" ,git)))
68-
    (home-page
69-
      "https://github.com/mgedmin/check-manifest")
70-
    (synopsis
71-
      "Check MANIFEST.in in a Python source package for completeness")
72-
    (description
73-
      "Check MANIFEST.in in a Python source package for completeness")
74-
    (license license:expat)))
75-
76-
(define-public python-codacy-coverage
77-
  (package
78-
    (name "python-codacy-coverage")
79-
    (version "1.3.11")
80-
    (source
81-
      (origin
82-
        (method url-fetch)
83-
        (uri (pypi-uri "codacy-coverage" version))
84-
        (sha256
85-
          (base32
86-
            "1g0c0w56xdkmqb8slacyw5qhzrkp814ng3ddh2lkiij58y9m2imr"))))
87-
    (build-system python-build-system)
88-
    (arguments
89-
     ;; No tests
90-
     `(#:tests? #f))
91-
    (propagated-inputs
92-
      `(("python-check-manifest" ,python-check-manifest)))
93-
    (home-page
94-
      "https://github.com/codacy/python-codacy-coverage")
95-
    (synopsis "Codacy coverage reporter for Python")
96-
    (description
97-
      "Codacy coverage reporter for Python")
98-
    (license license:expat)))
99-
100-
(define-public python-translation-finder
101-
  (package
102-
    (name "python-translation-finder")
103-
    (version "1.6")
104-
    (source
105-
      (origin
106-
        (method url-fetch)
107-
        (uri (pypi-uri "translation-finder" version))
108-
        (sha256
109-
          (base32
110-
            "0lq9441ziiq8aw8ldippkcvzhyw12lfra72kc6f5ik3rvw612m2a"))))
111-
    (build-system python-build-system)
112-
    (arguments
113-
     `(#:phases
114-
       (modify-phases %standard-phases
115-
         (add-before 'build 'remove-failing-test
116-
           (lambda _
117-
             (delete-file "translation_finder/test_api.py")
118-
             #t)))))
119-
    (propagated-inputs
120-
      `(("python-chardet" ,python-chardet)
121-
        ("python-pathlib2" ,python-pathlib2)
122-
        ("python-ruamel.yaml" ,python-ruamel.yaml)
123-
        ("python-six" ,python-six)))
124-
    (native-inputs
125-
     `(("python-codecov" ,python-codecov)
126-
       ("python-codacy-coverage" ,python-codacy-coverage)
127-
       ("python-pytest-cov" ,python-pytest-cov)
128-
       ("python-pytest-runner" ,python-pytest-runner)
129-
       ("python-twine" ,python-twine)))
130-
    (home-page "https://weblate.org/")
131-
    (synopsis
132-
      "A translation file finder for Weblate, translation tool with tight version control integration")
133-
    (description
134-
      "A translation file finder for Weblate, translation tool with tight version control integration")
135-
    (license license:gpl3+)))
136-
137-
(define-public python-httmock
138-
  (package
139-
    (name "python-httmock")
140-
    (version "1.3.0")
141-
    (source
142-
      (origin
143-
        (method url-fetch)
144-
        (uri (pypi-uri "httmock" version))
145-
        (sha256
146-
          (base32
147-
            "1zj1fcm0n6f0wr9mr0hmlqz9430fnr5cdwd5jkcvq9j44bnsrfz0"))))
148-
    (build-system python-build-system)
149-
    (arguments
150-
     ;; Tests can't be run?
151-
     `(#:tests? #f))
152-
    (propagated-inputs
153-
      `(("python-requests" ,python-requests)))
154-
    (home-page "https://github.com/patrys/httmock")
155-
    (synopsis "A mocking library for requests.")
156-
    (description "A mocking library for requests.")
157-
    (license license:asl2.0)))
158-
159-
(define-public python-gitlab
160-
  (package
161-
    (name "python-gitlab")
162-
    (version "1.10.0")
163-
    (source
164-
      (origin
165-
        (method url-fetch)
166-
        (uri (pypi-uri "python-gitlab" version))
167-
        (sha256
168-
          (base32
169-
            "0n2s4cmmrhx1yxpfa6xfkncairgrcvcmvhq2sx4k0k65cy9ipsf4"))))
170-
    (build-system python-build-system)
171-
    (propagated-inputs
172-
      `(("python-requests" ,python-requests)
173-
        ("python-six" ,python-six)))
174-
    (native-inputs
175-
     `(("python-httmock" ,python-httmock)
176-
       ("python-mock" ,python-mock)))
177-
    (home-page
178-
      "https://github.com/python-gitlab/python-gitlab")
179-
    (synopsis "Interact with GitLab API")
180-
    (description "Interact with GitLab API")
181-
    (license license:lgpl3+)))
182-
183-
(define-public python-android-stringslib
184-
  (package
185-
    (name "python-android-stringslib")
186-
    (version "0.1.2")
21+
    (name "offlate")
22+
    (version "0.5")
18723
    (source
18824
      (origin
18925
        (method git-fetch)
19026
        (uri (git-reference
191-
               (url "https://framagit.org/tyreunom/python-android-strings-lib")
192-
               (commit (string-append "v" version))))
27+
               (url "https://framagit.org/tyreunom/offlate")
28+
               (commit version)))
19329
        (file-name (git-file-name name version))
19430
        (sha256
19531
         (base32
196-
          "0gij55qzzq1h83kfpvhai1vf78kkhyvxa6l17m2nl24454lhfin4"))))
32+
          "13pqnbl05wcyldfvl75fp89vjgwsvxyc69vhnb17kkha2rc2k1h7"))))
19733
    (build-system python-build-system)
19834
    (arguments
35+
     ;; No tests
19936
     `(#:tests? #f))
200-
    (home-page
201-
      "https://framagit.org/tyreunom/python-android-strings-lib")
202-
    (synopsis
203-
      "Android strings.xml support")
204-
    (description
205-
      "Android Strings Lib provides support for android's strings.xml files.
206-
These files are used to translate strings in android apps.")
207-
    (license license:expat)))
208-
209-
(define-public python-pathtools
210-
  (package
211-
    (name "python-pathtools")
212-
    (version "0.1.2")
213-
    (source
214-
      (origin
215-
        (method url-fetch)
216-
        (uri (pypi-uri "pathtools" version))
217-
        (sha256
218-
          (base32
219-
            "1h7iam33vwxk8bvslfj4qlsdprdnwf8bvzhqh3jq5frr391cadbw"))))
220-
    (build-system python-build-system)
221-
    (home-page
222-
      "http://github.com/gorakhargosh/pathtools")
223-
    (synopsis "File system general utilities")
224-
    (description "File system general utilities")
225-
    (license #f)))
226-
227-
(define-public python-iocapture
228-
  (package
229-
    (name "python-iocapture")
230-
    (version "0.1.2")
231-
    (source
232-
      (origin
233-
        (method url-fetch)
234-
        (uri (pypi-uri "iocapture" version))
235-
        (sha256
236-
          (base32
237-
            "1s3ywdr0l3kfrrqi079iv16g0rp75akkvx0j07vx9p5w10c0wrw6"))))
238-
    (build-system python-build-system)
239-
    (native-inputs
240-
     `(("python-flexmock" ,python-flexmock)
241-
       ("python-pytest-cov" ,python-pytest-cov)
242-
       ("python-six" ,python-six)))
243-
    (home-page "https://github.com/oinume/iocapture")
244-
    (synopsis "Capture stdout, stderr easily.")
245-
    (description "Capture stdout, stderr easily.")
246-
    (license license:expat)))
247-
248-
(define-public python-argh
249-
  (package
250-
    (name "python-argh")
251-
    (version "0.26.2")
252-
    (source
253-
      (origin
254-
        (method url-fetch)
255-
        (uri (pypi-uri "argh" version))
256-
        (sha256
257-
          (base32
258-
            "0rdv0n2aa181mkrybwvl3czkrrikgzd4y2cri6j735fwhj65nlz9"))))
259-
    (build-system python-build-system)
260-
    (native-inputs
261-
     `(("python-iocapture" ,python-iocapture)
262-
       ("python-mock" ,python-mock)
263-
       ("python-pytest" ,python-pytest)))
264-
    (home-page "http://github.com/neithere/argh/")
265-
    (synopsis
266-
      "An unobtrusive argparse wrapper with natural syntax")
267-
    (description
268-
      "An unobtrusive argparse wrapper with natural syntax")
269-
    (license #f)))
270-
271-
(define-public python-watchdog
272-
  (package
273-
    (name "python-watchdog")
274-
    (version "0.9.0")
275-
    (source
276-
      (origin
277-
        (method url-fetch)
278-
        (uri (pypi-uri "watchdog" version))
279-
        (sha256
280-
          (base32
281-
            "07cnvvlpif7a6cg4rav39zq8fxa5pfqawchr46433pij0y6napwn"))))
282-
    (build-system python-build-system)
283-
    (arguments
284-
     `(#:phases
285-
       (modify-phases %standard-phases
286-
         (add-before 'check 'remove-failing
287-
           (lambda _
288-
             (delete-file "tests/test_inotify_buffer.py")
289-
             (delete-file "tests/test_snapshot_diff.py")
290-
             #t)))))
29137
    (propagated-inputs
292-
      `(("python-argh" ,python-argh)
293-
        ("python-pathtools" ,python-pathtools)
294-
        ("python-pyyaml" ,python-pyyaml)))
38+
      `(("python-android-stringslib" ,python-android-stringslib)
39+
        ("python-dateutil" ,python-dateutil)
40+
        ("python-gitlab" ,python-gitlab)
41+
        ("python-lxml" ,python-lxml)
42+
        ("python-polib" ,python-polib)
43+
        ("python-pyenchant" ,python-pyenchant)
44+
        ("python-pygit2" ,python-pygit2)
45+
        ("python-pygithub" ,python-pygithub)
46+
        ("python-pyqt" ,python-pyqt)
47+
        ("python-requests" ,python-requests)
48+
        ("python-ruamel.yaml" ,python-ruamel.yaml)
49+
        ("python-translation-finder" ,python-translation-finder)
50+
        ("python-watchdog" ,python-watchdog)))
29551
    (native-inputs
296-
     `(("python-pytest-cov" ,python-pytest-cov)
297-
       ("python-pytest-timeout" ,python-pytest-timeout)))
298-
    (home-page
299-
      "http://github.com/gorakhargosh/watchdog")
300-
    (synopsis "Filesystem events monitoring")
301-
    (description "Filesystem events monitoring")
302-
    (license #f)))
303-
304-
(define-public python-altgraph
305-
  (package
306-
    (name "python-altgraph")
307-
    (version "0.16.1")
308-
    (source
309-
      (origin
310-
        (method url-fetch)
311-
        (uri (pypi-uri "altgraph" version))
312-
        (sha256
313-
          (base32
314-
            "034vgy3nnm58rs4a6k83m9imayxx35k0p3hr22wafyql2w035xfx"))))
315-
    (build-system python-build-system)
316-
    (home-page "https://altgraph.readthedocs.io")
317-
    (synopsis "Python graph (network) package")
318-
    (description "Python graph (network) package")
319-
    (license license:expat)))
320-
321-
(define-public python-pyinstaller
322-
  (package
323-
    (name "python-pyinstaller")
324-
    (version "3.5")
325-
    (source
326-
      (origin
327-
        (method url-fetch)
328-
        (uri (pypi-uri "PyInstaller" version))
329-
        (sha256
330-
          (base32
331-
            "15ha3mmy4p93rpwcy2b3vcgwfsm5bq9z5yjh88ra6chk5l108xgf"))))
332-
    (build-system python-build-system)
333-
    (arguments
334-
     `(#:tests? #f
335-
       #:phases
336-
       (modify-phases %standard-phases
337-
         (delete 'validate-runpath))))
338-
    (propagated-inputs
339-
      `(("python-altgraph" ,python-altgraph)
340-
        ("python-setuptools" ,python-setuptools)))
341-
    (home-page "http://www.pyinstaller.org")
342-
    (synopsis
343-
      "PyInstaller bundles a Python application and all its dependencies into a single package.")
344-
    (description
345-
      "PyInstaller bundles a Python application and all its dependencies into a single package.")
346-
    (license #f)))
347-
348-
(package
349-
  (name "offlate")
350-
  (version "0.4")
351-
  (source
352-
    (origin
353-
      (method git-fetch)
354-
      (uri (git-reference
355-
             (url "https://framagit.org/tyreunom/offlate")
356-
             (commit version)))
357-
      (file-name (git-file-name name version))
358-
      (sha256
359-
       (base32
360-
        "10l03j8ajkd1a7sg1zycbpdaz71mscrncw7rwjzqk2ia6j04rwxm"))))
361-
  (build-system python-build-system)
362-
  (arguments
363-
   ;; No tests
364-
   `(#:tests? #f))
365-
  (propagated-inputs
366-
    `(("python-android-stringslib" ,python-android-stringslib)
367-
      ("python-dateutil" ,python-dateutil)
368-
      ("python-gitlab" ,python-gitlab)
369-
      ("python-lxml" ,python-lxml)
370-
      ("python-polib" ,python-polib)
371-
      ("python-pyenchant" ,python-pyenchant)
372-
      ("python-pygit2" ,python-pygit2)
373-
      ("python-pygithub" ,python-pygithub)
374-
      ("python-pyqt" ,python-pyqt)
375-
      ("python-requests" ,python-requests)
376-
      ("python-ruamel.yaml" ,python-ruamel.yaml)
377-
      ("python-translation-finder" ,python-translation-finder)
378-
      ("python-watchdog" ,python-watchdog)))
379-
  (native-inputs
380-
   `(("python-pyinstaller" ,python-pyinstaller)
381-
     ("qttools" ,qttools)))
382-
  (home-page
383-
    "https://framagit.org/tyreunom/offlate")
384-
  (synopsis
385-
    "Offline translation interface for online translation tools.")
386-
  (description
387-
    "Offline translation interface for online translation tools.")
388-
  (license license:gpl3+))
52+
     `(("fontforge" ,fontforge)
53+
       ("qttools" ,qttools)))
54+
    (home-page "https://framagit.org/tyreunom/offlate")
55+
    (synopsis "Offline translation interface for online translation tools")
56+
    (description "Offlate offers a unified interface for different translation
57+
file formats, as well as many different online translation platforms.  You can
58+
use it to get work from online platforms, specialized such as the Translation
59+
Project, or not such a gitlab instance when your upstream doesn't use any
60+
dedicated platform.  The tool proposes a unified interface for any format and
61+
an upload option to send your work back to the platform.")
62+
    (license license:gpl3+)))

offlate/ui/data/whitespace.sfd unknown status 1

1+
SplineFontDB: 3.2
2+
FontName: Whitespace
3+
FullName: Whitespace
4+
FamilyName: Whitespace
5+
Weight: Regular
6+
Copyright: Copyright (c) 2020, Julien Lepiller
7+
UComments: "2020-11-13: Created with FontForge (http://fontforge.org)"
8+
Version: 001.000
9+
ItalicAngle: 0
10+
UnderlinePosition: -102.4
11+
UnderlineWidth: 51.2
12+
Ascent: 819
13+
Descent: 205
14+
InvalidEm: 0
15+
LayerCount: 2
16+
Layer: 0 0 "Arri+AOgA-re" 1
17+
Layer: 1 0 "Avant" 0
18+
XUID: [1021 976 1692031623 15344048]
19+
StyleMap: 0x0000
20+
FSType: 0
21+
OS2Version: 0
22+
OS2_WeightWidthSlopeOnly: 0
23+
OS2_UseTypoMetrics: 1
24+
CreationTime: 1605296929
25+
ModificationTime: 1605301836
26+
OS2TypoAscent: 0
27+
OS2TypoAOffset: 1
28+
OS2TypoDescent: 0
29+
OS2TypoDOffset: 1
30+
OS2TypoLinegap: 92
31+
OS2WinAscent: 0
32+
OS2WinAOffset: 1
33+
OS2WinDescent: 0
34+
OS2WinDOffset: 1
35+
HheadAscent: 0
36+
HheadAOffset: 1
37+
HheadDescent: 0
38+
HheadDOffset: 1
39+
MarkAttachClasses: 1
40+
DEI: 91125
41+
Encoding: UnicodeFull
42+
UnicodeInterp: none
43+
NameList: AGL For New Fonts
44+
DisplaySize: -48
45+
AntiAlias: 1
46+
FitToEm: 0
47+
WinInfo: 7980 38 15
48+
BeginPrivate: 0
49+
EndPrivate
50+
Grid
51+
-1024 402.431640625 m 0
52+
 2048 402.431640625 l 1024
53+
EndSplineSet
54+
BeginChars: 1114112 4
55+
56+
StartChar: space
57+
Encoding: 32 32 0
58+
Width: 196
59+
Flags: HW
60+
HStem: 216 92
61+
VStem: 44 93
62+
LayerCount: 2
63+
Fore
64+
SplineSet
65+
57 297 m 5
66+
 104 308 l 5
67+
 137 273 l 5
68+
 123 227 l 5
69+
 76 216 l 5
70+
 44 251 l 5
71+
 57 297 l 5
72+
EndSplineSet
73+
EndChar
74+
75+
StartChar: uni00A0
76+
Encoding: 160 160 1
77+
Width: 399
78+
InSpiro: 1
79+
Flags: HW
80+
HStem: 55 55<72 313>
81+
VStem: 41 31<111 230> 313 31<106 240>
82+
LayerCount: 2
83+
Fore
84+
SplineSet
85+
40.9599609375 230.400390625 m 1
86+
 71.6796875 230.400390625 l 0
87+
 71.6796875 110.591796875 l 1
88+
 313.34375 106.49609375 l 25
89+
 313.34375 239.616210938 l 0
90+
 344.064453125 239.616210938 l 0
91+
 344.064453125 55.2958984375 l 0
92+
 40.9599609375 55.2958984375 l 0
93+
 40.9599609375 230.400390625 l 1
94+
EndSplineSet
95+
EndChar
96+
97+
StartChar: uni0009
98+
Encoding: 9 9 2
99+
Width: 1024
100+
Flags: HW
101+
HStem: 178 53<226 568> 518 53<424 766>
102+
VStem: 237 57<433 540 549 675> 698 57<93 200 209 335>
103+
LayerCount: 2
104+
Fore
105+
SplineSet
106+
756 335 m 1
107+
 756 93 l 0
108+
 698 93 l 0
109+
 698 200 l 25
110+
 569 93 l 0
111+
 568 178 l 0
112+
 226 180 l 0
113+
 226 233 l 0
114+
 568 231 l 0
115+
 568 335 l 0
116+
 697 209 l 25
117+
 698 335 l 0
118+
 756 335 l 1
119+
237 675 m 1
120+
 294 675 l 0
121+
 295 549 l 25
122+
 424 675 l 0
123+
 424 571 l 0
124+
 766 573 l 0
125+
 766 520 l 0
126+
 424 518 l 0
127+
 423 433 l 0
128+
 294 540 l 25
129+
 294 433 l 0
130+
 237 433 l 0
131+
 237 675 l 1
132+
EndSplineSet
133+
EndChar
134+
135+
StartChar: uni202F
136+
Encoding: 8239 8239 3
137+
Width: 229
138+
Flags: HW
139+
HStem: 55 55<61 160>
140+
VStem: 31 31<111 230> 160 31<111 229>
141+
LayerCount: 2
142+
Fore
143+
SplineSet
144+
31 230 m 1
145+
 61 230 l 0
146+
 61 111 l 1
147+
 160 111 l 25
148+
 160 229 l 0
149+
 190 229 l 0
150+
 190 55 l 0
151+
 31 55 l 0
152+
 31 230 l 1
153+
EndSplineSet
154+
EndChar
155+
EndChars
156+
EndSplineFont

offlate/ui/spellcheckedit.py

2121
import enchant
2222
import re
2323
import sys
24+
import os
2425
2526
class SpellCheckEdit(QTextEdit):
2627
    def __init__(self, lang, *args):

7071
        cursor.endEditBlock()
7172
7273
class Highlighter(QSyntaxHighlighter):
74+
    _spaceFontFamily = None
75+
7376
    def __init__(self, *args):
7477
        QSyntaxHighlighter.__init__(self, *args)
7578

8891
            if not self.dict.check(word_object.group()):
8992
                self.setFormat(word_object.start(), word_object.end() - word_object.start(), format)
9093
94+
        format = QTextCharFormat()
95+
        format.setFont(QFont(Highlighter.getSpaceFontFamily()))
96+
        format.setForeground(Qt.gray)
97+
98+
        for word_object in re.finditer(r'\s+', text):
99+
            self.setFormat(word_object.start(), word_object.end() - word_object.start(), format)
100+
101+
    @staticmethod
102+
    def getSpaceFontFamily():
103+
        font = Highlighter._spaceFontFamily
104+
        if font is None:
105+
            id = QFontDatabase.addApplicationFont(os.path.dirname(__file__) + '/data/whitespace.ttf')
106+
            font = QFontDatabase.applicationFontFamilies(id)[0]
107+
        return font
91108
92109
if __name__ == '__main__':
93110
    app = QApplication(sys.argv)

setup.py

88
        command = ["make" "update-langs"]
99
        subprocess.check_call(command)
1010
11+
class FontsCommand(distutils.cmd.Command):
12+
    description='generate font files'
13+
    def run(self):
14+
        command = ["make" "fonts"]
15+
        subprocess.check_call(command)
1116
1217
version_file = open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'offlate', 'VERSION'))
1318
version = version_file.read().strip()

2934
    package_data={'offlate': ['data.json', 'locales/*.qm', 'locales/*.ts', 'icon.png', 'VERSION']},
3035
    cmdclass={
3136
        'locales': LocalesCommand,
37+
        'fonts': FontsCommand,
3238
    },
3339
3440
    author="Julien Lepiller",