Add transifex system

Julien LepillerThu Apr 19 12:41:11+0200 2018

61d45a8

Add transifex system Add entry system for different string entry type, use ruamel for yaml. The Transifex system currently only supports yaml format.

guix-full.manifest

1717
   "python-neovim"
1818
   "python-polib"
1919
   "python-pyqt"
20+
   "python-ruamel.yaml"
2021
   "python-sphinx"
2122
   "python-twine"
2223
   "qt"

guix.manifest

66
   "python-lxml"
77
   "python-polib"
88
   "python-pyqt"
9+
   "python-ruamel.yaml"
910
   "python-sphinx"
1011
   "python-twine"))

i18n/offlate_fr.ts

33
<context>
44
    <name>NewWindow</name>
55
    <message>
6-
        <location filename="../offlate/window.py" line="190"/>
6+
        <location filename="../offlate/window.py" line="202"/>
77
        <source>The Translation Project</source>
88
        <translation>Le Projet de Traduction</translation>
99
    </message>
1010
    <message>
11-
        <location filename="../offlate/window.py" line="193"/>
11+
        <location filename="../offlate/window.py" line="207"/>
1212
        <source>Cancel</source>
1313
        <translation>Annuler</translation>
1414
    </message>
1515
    <message>
16-
        <location filename="../offlate/window.py" line="194"/>
16+
        <location filename="../offlate/window.py" line="208"/>
1717
        <source>OK</source>
1818
        <translation>OK</translation>
1919
    </message>
20+
    <message>
21+
        <location filename="../offlate/window.py" line="193"/>
22+
        <source>Project information</source>
23+
        <translation type="unfinished"></translation>
24+
    </message>
25+
    <message>
26+
        <location filename="../offlate/window.py" line="199"/>
27+
        <source>Name:</source>
28+
        <translation type="unfinished"></translation>
29+
    </message>
30+
    <message>
31+
        <location filename="../offlate/window.py" line="200"/>
32+
        <source>Target Language:</source>
33+
        <translation type="unfinished"></translation>
34+
    </message>
35+
    <message>
36+
        <location filename="../offlate/window.py" line="203"/>
37+
        <source>Transifex</source>
38+
        <translation type="unfinished"></translation>
39+
    </message>
40+
    <message>
41+
        <location filename="../offlate/window.py" line="220"/>
42+
        <source>Organization</source>
43+
        <translation type="unfinished"></translation>
44+
    </message>
2045
</context>
2146
<context>
2247
    <name>ProjectView</name>
2348
    <message>
24-
        <location filename="../offlate/window.py" line="103"/>
49+
        <location filename="../offlate/window.py" line="110"/>
2550
        <source>Singular</source>
2651
        <translation>Singulier</translation>
2752
    </message>
2853
    <message>
29-
        <location filename="../offlate/window.py" line="104"/>
54+
        <location filename="../offlate/window.py" line="111"/>
3055
        <source>Plural</source>
3156
        <translation>Pluriel</translation>
3257
    </message>

3459
<context>
3560
    <name>SettingsWindow</name>
3661
    <message>
37-
        <location filename="../offlate/window.py" line="254"/>
62+
        <location filename="../offlate/window.py" line="298"/>
3863
        <source>Cancel</source>
3964
        <translation>Annuler</translation>
4065
    </message>
4166
    <message>
42-
        <location filename="../offlate/window.py" line="255"/>
67+
        <location filename="../offlate/window.py" line="299"/>
4368
        <source>OK</source>
4469
        <translation>OK</translation>
4570
    </message>
4671
    <message>
47-
        <location filename="../offlate/window.py" line="283"/>
72+
        <location filename="../offlate/window.py" line="360"/>
4873
        <source>Email:</source>
4974
        <translation>Adresse de courriel :</translation>
5075
    </message>
5176
    <message>
52-
        <location filename="../offlate/window.py" line="284"/>
77+
        <location filename="../offlate/window.py" line="361"/>
5378
        <source>Server:</source>
5479
        <translation>Serveur :</translation>
5580
    </message>
5681
    <message>
57-
        <location filename="../offlate/window.py" line="285"/>
82+
        <location filename="../offlate/window.py" line="362"/>
5883
        <source>User Name:</source>
5984
        <translation>Nom d&apos;utilisateur :</translation>
6085
    </message>
86+
    <message>
87+
        <location filename="../offlate/window.py" line="310"/>
88+
        <source>Transifex</source>
89+
        <translation type="unfinished"></translation>
90+
    </message>
91+
    <message>
92+
        <location filename="../offlate/window.py" line="322"/>
93+
        <source>You can get a token from &lt;a href=&quot;#&quot;&gt;https://www.transifex.com/user/settings/api/&lt;/a&gt;</source>
94+
        <translation type="unfinished"></translation>
95+
    </message>
96+
    <message>
97+
        <location filename="../offlate/window.py" line="325"/>
98+
        <source>Token:</source>
99+
        <translation type="unfinished"></translation>
100+
    </message>
101+
    <message>
102+
        <location filename="../offlate/window.py" line="339"/>
103+
        <source>Translation Project</source>
104+
        <translation type="unfinished"></translation>
105+
    </message>
61106
</context>
62107
<context>
63108
    <name>Window</name>
64109
    <message>
65-
        <location filename="../offlate/window.py" line="356"/>
110+
        <location filename="../offlate/window.py" line="433"/>
66111
        <source>Exit application</source>
67112
        <translation>Quitter l&apos;application</translation>
68113
    </message>
69114
    <message>
70-
        <location filename="../offlate/window.py" line="361"/>
115+
        <location filename="../offlate/window.py" line="438"/>
71116
        <source>Save current project</source>
72117
        <translation>Sauvegarder le projet actuel</translation>
73118
    </message>
74119
    <message>
75-
        <location filename="../offlate/window.py" line="366"/>
120+
        <location filename="../offlate/window.py" line="443"/>
76121
        <source>New project</source>
77122
        <translation>Nouveau projet</translation>
78123
    </message>
79124
    <message>
80-
        <location filename="../offlate/window.py" line="371"/>
125+
        <location filename="../offlate/window.py" line="448"/>
81126
        <source>Get modifications from upstream</source>
82127
        <translation>R??cup??rer les modifications en amont</translation>
83128
    </message>
84129
    <message>
85-
        <location filename="../offlate/window.py" line="375"/>
130+
        <location filename="../offlate/window.py" line="452"/>
86131
        <source>Close current project</source>
87132
        <translation>Fermer le projet actuel</translation>
88133
    </message>
89134
    <message>
90-
        <location filename="../offlate/window.py" line="380"/>
135+
        <location filename="../offlate/window.py" line="457"/>
91136
        <source>Send modifications upstream</source>
92137
        <translation>Envoyer les modifications en amont</translation>
93138
    </message>
94139
    <message>
95-
        <location filename="../offlate/window.py" line="385"/>
140+
        <location filename="../offlate/window.py" line="462"/>
96141
        <source>Set parameters</source>
97142
        <translation>Configurer les param??tres</translation>
98143
    </message>
99144
    <message>
100-
        <location filename="../offlate/window.py" line="388"/>
145+
        <location filename="../offlate/window.py" line="465"/>
101146
        <source>Show Translated</source>
102147
        <translation>Montrer traduites</translation>
103148
    </message>
104149
    <message>
105-
        <location filename="../offlate/window.py" line="391"/>
150+
        <location filename="../offlate/window.py" line="468"/>
106151
        <source>Show Fuzzy</source>
107152
        <translation>Montrer floues</translation>
108153
    </message>
109154
    <message>
110-
        <location filename="../offlate/window.py" line="394"/>
155+
        <location filename="../offlate/window.py" line="471"/>
111156
        <source>Show Empty Translation</source>
112157
        <translation>Montrer les traductions vides</translation>
113158
    </message>
114159
    <message>
115-
        <location filename="../offlate/window.py" line="404"/>
160+
        <location filename="../offlate/window.py" line="481"/>
116161
        <source>&amp;File</source>
117162
        <translation>&amp;Fichier</translation>
118163
    </message>
119164
    <message>
120-
        <location filename="../offlate/window.py" line="410"/>
165+
        <location filename="../offlate/window.py" line="487"/>
121166
        <source>&amp;Project</source>
122167
        <translation>&amp;Projet</translation>
123168
    </message>
124169
    <message>
125-
        <location filename="../offlate/window.py" line="417"/>
170+
        <location filename="../offlate/window.py" line="494"/>
126171
        <source>&amp;Edit</source>
127172
        <translation>&amp;??dition</translation>
128173
    </message>
129174
    <message>
130-
        <location filename="../offlate/window.py" line="420"/>
175+
        <location filename="../offlate/window.py" line="497"/>
131176
        <source>&amp;View</source>
132177
        <translation>&amp;Affichage</translation>
133178
    </message>
134179
    <message>
135-
        <location filename="../offlate/window.py" line="400"/>
180+
        <location filename="../offlate/window.py" line="477"/>
136181
        <source>Open</source>
137182
        <translation>Ouvrir</translation>
138183
    </message>
139184
    <message>
140-
        <location filename="../offlate/window.py" line="354"/>
185+
        <location filename="../offlate/window.py" line="431"/>
141186
        <source>Exit</source>
142187
        <translation>Quitter</translation>
143188
    </message>
144189
    <message>
145-
        <location filename="../offlate/window.py" line="359"/>
190+
        <location filename="../offlate/window.py" line="436"/>
146191
        <source>Save</source>
147192
        <translation>Sauvegarder</translation>
148193
    </message>
149194
    <message>
150-
        <location filename="../offlate/window.py" line="364"/>
195+
        <location filename="../offlate/window.py" line="441"/>
151196
        <source>New</source>
152197
        <translation>Nouveau</translation>
153198
    </message>
154199
    <message>
155-
        <location filename="../offlate/window.py" line="369"/>
200+
        <location filename="../offlate/window.py" line="446"/>
156201
        <source>Update</source>
157202
        <translation>Mettre ?? jour</translation>
158203
    </message>
159204
    <message>
160-
        <location filename="../offlate/window.py" line="374"/>
205+
        <location filename="../offlate/window.py" line="451"/>
161206
        <source>Close</source>
162207
        <translation>Fermer</translation>
163208
    </message>
164209
    <message>
165-
        <location filename="../offlate/window.py" line="378"/>
210+
        <location filename="../offlate/window.py" line="455"/>
166211
        <source>Send</source>
167212
        <translation>Envoyer</translation>
168213
    </message>
169214
    <message>
170-
        <location filename="../offlate/window.py" line="383"/>
215+
        <location filename="../offlate/window.py" line="460"/>
171216
        <source>Settings</source>
172217
        <translation>Param??tres</translation>
173218
    </message>
174219
</context>
220+
<context>
221+
    <name>self.qd</name>
222+
    <message>
223+
        <location filename="../offlate/window.py" line="24"/>
224+
        <source>Please enter your password:</source>
225+
        <translation type="unfinished"></translation>
226+
    </message>
227+
</context>
175228
</TS>

offlate/data.json

1-
[{"name": "a2ps", "system": 0}, {"name": "aegis", "system": 0}, {"name": "anubis", "system": 0}, {"name": "aspell", "system": 0}, {"name": "bash", "system": 0}, {"name": "bfd", "system": 0}, {"name": "binutils", "system": 0}, {"name": "bison", "system": 0}, {"name": "bison-runtime", "system": 0}, {"name": "buzztrax", "system": 0}, {"name": "ccd2cue", "system": 0}, {"name": "ccide", "system": 0}, {"name": "cflow", "system": 0}, {"name": "clisp", "system": 0}, {"name": "coreutils", "system": 0}, {"name": "cpio", "system": 0}, {"name": "cppi", "system": 0}, {"name": "cpplib", "system": 0}, {"name": "cryptsetup", "system": 0}, {"name": "datamash", "system": 0}, {"name": "denemo", "system": 0}, {"name": "dfarc", "system": 0}, {"name": "dialog", "system": 0}, {"name": "dico", "system": 0}, {"name": "diffutils", "system": 0}, {"name": "dink", "system": 0}, {"name": "direvent", "system": 0}, {"name": "doodle", "system": 0}, {"name": "dos2unix", "system": 0}, {"name": "dos2unix-man", "system": 0}, {"name": "e2fsprogs", "system": 0}, {"name": "enscript", "system": 0}, {"name": "exif", "system": 0}, {"name": "fetchmail", "system": 0}, {"name": "findutils", "system": 0}, {"name": "flex", "system": 0}, {"name": "freedink", "system": 0}, {"name": "fusionforge", "system": 0}, {"name": "gas", "system": 0}, {"name": "gawk", "system": 0}, {"name": "gcal", "system": 0}, {"name": "gcc", "system": 0}, {"name": "gdbm", "system": 0}, {"name": "gettext-examples", "system": 0}, {"name": "gettext-runtime", "system": 0}, {"name": "gettext-tools", "system": 0}, {"name": "gjay", "system": 0}, {"name": "glunarclock", "system": 0}, {"name": "gnubiff", "system": 0}, {"name": "gnubik", "system": 0}, {"name": "gnucash", "system": 0}, {"name": "gnuchess", "system": 0}, {"name": "gnucobol", "system": 0}, {"name": "gnulib", "system": 0}, {"name": "gnunet", "system": 0}, {"name": "gnunet-gtk", "system": 0}, {"name": "gold", "system": 0}, {"name": "gphoto2", "system": 0}, {"name": "gprof", "system": 0}, {"name": "gramadoir", "system": 0}, {"name": "grep", "system": 0}, {"name": "grip", "system": 0}, {"name": "grub", "system": 0}, {"name": "gsasl", "system": 0}, {"name": "gss", "system": 0}, {"name": "gst-plugins-bad", "system": 0}, {"name": "gst-plugins-base", "system": 0}, {"name": "gst-plugins-good", "system": 0}, {"name": "gst-plugins-ugly", "system": 0}, {"name": "gstreamer", "system": 0}, {"name": "gtick", "system": 0}, {"name": "gtkam", "system": 0}, {"name": "gtkspell", "system": 0}, {"name": "guix", "system": 0}, {"name": "guix-packages", "system": 0}, {"name": "gutenprint", "system": 0}, {"name": "hello", "system": 0}, {"name": "help2man", "system": 0}, {"name": "help2man-texi", "system": 0}, {"name": "hylafax", "system": 0}, {"name": "idutils", "system": 0}, {"name": "jwhois", "system": 0}, {"name": "kbd", "system": 0}, {"name": "klavaro", "system": 0}, {"name": "ld", "system": 0}, {"name": "leafpad", "system": 0}, {"name": "libc", "system": 0}, {"name": "libexif", "system": 0}, {"name": "libextractor", "system": 0}, {"name": "libgnutls", "system": 0}, {"name": "libgphoto2", "system": 0}, {"name": "libgphoto2_port", "system": 0}, {"name": "libgsasl", "system": 0}, {"name": "libiconv", "system": 0}, {"name": "libidn", "system": 0}, {"name": "libidn2", "system": 0}, {"name": "lilypond", "system": 0}, {"name": "lordsawar", "system": 0}, {"name": "lprng", "system": 0}, {"name": "lynx", "system": 0}, {"name": "m4", "system": 0}, {"name": "mailfromd", "system": 0}, {"name": "mailutils", "system": 0}, {"name": "make", "system": 0}, {"name": "man-db", "system": 0}, {"name": "man-db-manpages", "system": 0}, {"name": "midi-instruments", "system": 0}, {"name": "minicom", "system": 0}, {"name": "mkisofs", "system": 0}, {"name": "muibase", "system": 0}, {"name": "myserver", "system": 0}, {"name": "nano", "system": 0}, {"name": "opcodes", "system": 0}, {"name": "parted", "system": 0}, {"name": "pies", "system": 0}, {"name": "pnmixer", "system": 0}, {"name": "popt", "system": 0}, {"name": "procps-ng", "system": 0}, {"name": "procps-ng-man", "system": 0}, {"name": "psmisc", "system": 0}, {"name": "pspp", "system": 0}, {"name": "pushover", "system": 0}, {"name": "pwdutils", "system": 0}, {"name": "pyspread", "system": 0}, {"name": "radius", "system": 0}, {"name": "recode", "system": 0}, {"name": "recutils", "system": 0}, {"name": "rpm", "system": 0}, {"name": "rush", "system": 0}, {"name": "sarg", "system": 0}, {"name": "savane", "system": 0}, {"name": "sed", "system": 0}, {"name": "sharutils", "system": 0}, {"name": "shepherd", "system": 0}, {"name": "shishi", "system": 0}, {"name": "skribilo", "system": 0}, {"name": "solfege", "system": 0}, {"name": "solfege-manual", "system": 0}, {"name": "spotmachine", "system": 0}, {"name": "sudo", "system": 0}, {"name": "sudoers", "system": 0}, {"name": "sysstat", "system": 0}, {"name": "tar", "system": 0}, {"name": "texinfo", "system": 0}, {"name": "texinfo_document", "system": 0}, {"name": "tigervnc", "system": 0}, {"name": "tin", "system": 0}, {"name": "tin-man", "system": 0}, {"name": "tracgoogleappsauthplugin", "system": 0}, {"name": "trader", "system": 0}, {"name": "util-linux", "system": 0}, {"name": "ve", "system": 0}, {"name": "vmm", "system": 0}, {"name": "vorbis-tools", "system": 0}, {"name": "wastesedge", "system": 0}, {"name": "wcd", "system": 0}, {"name": "wcd-man", "system": 0}, {"name": "wdiff", "system": 0}, {"name": "wget", "system": 0}, {"name": "wyslij-po", "system": 0}, {"name": "xboard", "system": 0}, {"name": "xdg-user-dirs", "system": 0}, {"name": "xkeyboard-config", "system": 0}]
1>
20>
\ No newline at end of file
1+
[{"name": "a2ps", "system": 0}, {"name": "aegis", "system": 0}, {"name": "anubis", "system": 0}, {"name": "aspell", "system": 0}, {"name": "bash", "system": 0}, {"name": "bfd", "system": 0}, {"name": "binutils", "system": 0}, {"name": "bison", "system": 0}, {"name": "bison-runtime", "system": 0}, {"name": "buzztrax", "system": 0}, {"name": "ccd2cue", "system": 0}, {"name": "ccide", "system": 0}, {"name": "cflow", "system": 0}, {"name": "clisp", "system": 0}, {"name": "coreutils", "system": 0}, {"name": "cpio", "system": 0}, {"name": "cppi", "system": 0}, {"name": "cpplib", "system": 0}, {"name": "cryptsetup", "system": 0}, {"name": "datamash", "system": 0}, {"name": "denemo", "system": 0}, {"name": "dfarc", "system": 0}, {"name": "dialog", "system": 0}, {"name": "dico", "system": 0}, {"name": "diffutils", "system": 0}, {"name": "dink", "system": 0}, {"name": "direvent", "system": 0}, {"name": "doodle", "system": 0}, {"name": "dos2unix", "system": 0}, {"name": "dos2unix-man", "system": 0}, {"name": "e2fsprogs", "system": 0}, {"name": "enscript", "system": 0}, {"name": "exif", "system": 0}, {"name": "fetchmail", "system": 0}, {"name": "findutils", "system": 0}, {"name": "flex", "system": 0}, {"name": "freedink", "system": 0}, {"name": "fusionforge", "system": 0}, {"name": "gas", "system": 0}, {"name": "gawk", "system": 0}, {"name": "gcal", "system": 0}, {"name": "gcc", "system": 0}, {"name": "gdbm", "system": 0}, {"name": "gettext-examples", "system": 0}, {"name": "gettext-runtime", "system": 0}, {"name": "gettext-tools", "system": 0}, {"name": "gjay", "system": 0}, {"name": "glunarclock", "system": 0}, {"name": "gnubiff", "system": 0}, {"name": "gnubik", "system": 0}, {"name": "gnucash", "system": 0}, {"name": "gnuchess", "system": 0}, {"name": "gnucobol", "system": 0}, {"name": "gnulib", "system": 0}, {"name": "gnunet", "system": 0}, {"name": "gnunet-gtk", "system": 0}, {"name": "gold", "system": 0}, {"name": "gphoto2", "system": 0}, {"name": "gprof", "system": 0}, {"name": "gramadoir", "system": 0}, {"name": "grep", "system": 0}, {"name": "grip", "system": 0}, {"name": "grub", "system": 0}, {"name": "gsasl", "system": 0}, {"name": "gss", "system": 0}, {"name": "gst-plugins-bad", "system": 0}, {"name": "gst-plugins-base", "system": 0}, {"name": "gst-plugins-good", "system": 0}, {"name": "gst-plugins-ugly", "system": 0}, {"name": "gstreamer", "system": 0}, {"name": "gtick", "system": 0}, {"name": "gtkam", "system": 0}, {"name": "gtkspell", "system": 0}, {"name": "guix", "system": 0}, {"name": "guix-packages", "system": 0}, {"name": "gutenprint", "system": 0}, {"name": "hello", "system": 0}, {"name": "help2man", "system": 0}, {"name": "help2man-texi", "system": 0}, {"name": "hylafax", "system": 0}, {"name": "idutils", "system": 0}, {"name": "jwhois", "system": 0}, {"name": "kbd", "system": 0}, {"name": "klavaro", "system": 0}, {"name": "ld", "system": 0}, {"name": "leafpad", "system": 0}, {"name": "libc", "system": 0}, {"name": "libexif", "system": 0}, {"name": "libextractor", "system": 0}, {"name": "libgnutls", "system": 0}, {"name": "libgphoto2", "system": 0}, {"name": "libgphoto2_port", "system": 0}, {"name": "libgsasl", "system": 0}, {"name": "libiconv", "system": 0}, {"name": "libidn", "system": 0}, {"name": "libidn2", "system": 0}, {"name": "lilypond", "system": 0}, {"name": "lordsawar", "system": 0}, {"name": "lprng", "system": 0}, {"name": "lynx", "system": 0}, {"name": "m4", "system": 0}, {"name": "mailfromd", "system": 0}, {"name": "mailutils", "system": 0}, {"name": "make", "system": 0}, {"name": "man-db", "system": 0}, {"name": "man-db-manpages", "system": 0}, {"name": "midi-instruments", "system": 0}, {"name": "minicom", "system": 0}, {"name": "mkisofs", "system": 0}, {"name": "muibase", "system": 0}, {"name": "myserver", "system": 0}, {"name": "nano", "system": 0}, {"name": "opcodes", "system": 0}, {"name": "parted", "system": 0}, {"name": "pies", "system": 0}, {"name": "pnmixer", "system": 0}, {"name": "popt", "system": 0}, {"name": "procps-ng", "system": 0}, {"name": "procps-ng-man", "system": 0}, {"name": "psmisc", "system": 0}, {"name": "pspp", "system": 0}, {"name": "pushover", "system": 0}, {"name": "pwdutils", "system": 0}, {"name": "pyspread", "system": 0}, {"name": "radius", "system": 0}, {"name": "recode", "system": 0}, {"name": "recutils", "system": 0}, {"name": "rpm", "system": 0}, {"name": "rush", "system": 0}, {"name": "sarg", "system": 0}, {"name": "savane", "system": 0}, {"name": "sed", "system": 0}, {"name": "sharutils", "system": 0}, {"name": "shepherd", "system": 0}, {"name": "shishi", "system": 0}, {"name": "skribilo", "system": 0}, {"name": "solfege", "system": 0}, {"name": "solfege-manual", "system": 0}, {"name": "spotmachine", "system": 0}, {"name": "sudo", "system": 0}, {"name": "sudoers", "system": 0}, {"name": "sysstat", "system": 0}, {"name": "tar", "system": 0}, {"name": "texinfo", "system": 0}, {"name": "texinfo_document", "system": 0}, {"name": "tigervnc", "system": 0}, {"name": "tin", "system": 0}, {"name": "tin-man", "system": 0}, {"name": "tracgoogleappsauthplugin", "system": 0}, {"name": "trader", "system": 0}, {"name": "util-linux", "system": 0}, {"name": "ve", "system": 0}, {"name": "vmm", "system": 0}, {"name": "vorbis-tools", "system": 0}, {"name": "wastesedge", "system": 0}, {"name": "wcd", "system": 0}, {"name": "wcd-man", "system": 0}, {"name": "wdiff", "system": 0}, {"name": "wget", "system": 0}, {"name": "wyslij-po", "system": 0}, {"name": "xboard", "system": 0}, {"name": "xdg-user-dirs", "system": 0}, {"name": "xkeyboard-config", "system": 0}, {"name": "id-editor", "system": 1, "organisation": "openstreetmap"}]

offlate/manager.py

11
from pathlib import Path
22
from .systems.tp import TPProject
3+
from .systems.transifex import TransifexProject
34
45
import json
56
import os

3940
                        if not "TP" in self.settings.conf:
4041
                            self.settings.conf["TP"] = {}
4142
                        self.project_list[p['name']] = \
42-
                            TPProject(self.settings.conf["TP"], p['name'], p['lang'])
43-
                        self.project_list[p['name']].open(self.basedir+'/'+p['name'], p['info']['version'])
44-
        except Exception:
43+
                            TPProject(self.settings.conf["TP"], p['name'], p['lang'], p['info'])
44+
                        self.project_list[p['name']].open(self.basedir+'/'+p['name'])
45+
                    if p['system'] == 1:
46+
                        if not "Transifex" in self.settings.conf:
47+
                            self.settings.conf['Transifex'] = {}
48+
                        self.project_list[p['name']] = \
49+
                            TransifexProject(self.settings.conf['Transifex'], p['name'], p['lang'], p['info'])
50+
                        self.project_list[p['name']].open(self.basedir+'/'+p['name'])
51+
        except Exception as e:
52+
            print(e)
4553
            with open(self.basedir + '/projects.json', 'w') as f:
4654
                f.write(json.dumps([]))
4755
48-
    def createProject(self, name, lang, system):
56+
    def createProject(self, name, lang, system, data):
4957
        projectpath = self.basedir + '/' + name
5058
        Path(projectpath).mkdir(parents=True)
5159
        if system == 0: #TP

5664
            self.project_list[name] = proj
5765
            self.projects.append({"name": name, "lang": lang, "system": system,
5866
                    "info": {"version": proj.version}})
67+
        if system == 1: #Transifex
68+
            if not 'Transifex' in self.settings.conf:
69+
                self.settings.conf['Transifex'] = {}
70+
            proj = TransifexProject(self.settings.conf['Transifex'], name, lang, data)
71+
            proj.initialize(projectpath)
72+
            self.project_list[name] = proj
73+
            self.projects.append({"name": name, "lang": lang, "system": system,
74+
                    "info": data})
5975
        self.writeProjects()
6076
6177
    def writeProjects(self):

offlate/systems/entry.py unknown status 1

1+
class Entry:
2+
    def __init__(self, msgids, msgstrs, fuzzy, obsolete):
3+
        self.msgids = msgids
4+
        self.msgstrs = msgstrs
5+
        self.fuzzy = fuzzy
6+
        self.obsolete = obsolete
7+
8+
    def isTranslated(self):
9+
        for msgstr in self.msgstrs:
10+
            if msgstr == '':
11+
                return False
12+
        return True
13+
14+
    def isFuzzy(self):
15+
        return self.fuzzy
16+
17+
    def isObsolete(self):
18+
        return self.obsolete
19+
20+
    def update(self, index, content):
21+
        self.msgstrs[index] = content
22+
23+
class POEntry(Entry):
24+
    def __init__(self, entry):
25+
        msgids = [entry.msgid]
26+
        msgstrs = [entry.msgstr]
27+
        if 0 in entry.msgstr_plural:
28+
            msgstrs = []
29+
            for msgstr in entry.msgstr_plural:
30+
                msgstrs.append(entry.msgstr_plural[msgstr])
31+
            msgids = [entry.msgid, entry.msgid_plural]
32+
        Entry.__init__(self, msgids, msgstrs, "fuzzy" in entry.flags, entry.obsolete)
33+
        self.entry = entry
34+
35+
    def update(self, index, content):
36+
        Entry.update(self, index, content)
37+
        if 0 in self.entry.msgstr_plural:
38+
            self.entry.msgstr_plural[index] = content
39+
        else:
40+
            self.entry.msgstr = content
41+
42+
class JSONEntry(Entry):
43+
    def __init__(self, entry):
44+
        Entry.__init__(self, [entry['source_string']], [entry['translation']], False, False)
45+
        self.entry = entry
46+
47+
    def update(self, index, content):
48+
        Entry.update(self, index, content)
49+
        self.entry['translation'] = content
50+
51+
class YAMLEntry(Entry):
52+
    def __init__(self, entry):
53+
        self.key = list(entry.keys())[0]
54+
        Entry.__init__(self, self.key, entry[self.key], False, False)
55+
        self.entry = entry
56+
57+
    def update(self, index, content):
58+
        Entry.update(self, index, content)
59+
        self.entry[self.key] = content

offlate/systems/tp.py

1111
import os
1212
from pathlib import Path
1313
14+
from .entry import POEntry
15+
1416
class TPProject:
15-
    def __init__(self, conf, name, lang):
17+
    def __init__(self, conf, name, lang, data = {}):
1618
        self.uri = "https://translationproject.org"
1719
        self.conf = conf
1820
        self.name = name
1921
        self.lang = lang
2022
        self.basedir = ''
23+
        self.info = data
24+
        if "version" in data:
25+
            self.version = data['version']
2126
22-
    def open(self, basedir, version):
23-
        self.version = version
27+
    def open(self, basedir):
2428
        self.basedir = basedir
2529
        self.updateFileName()
2630
        self.updateGettextNames()

104108
        newpo.save()
105109
106110
    def send(self, interface):
111+
        self.save()
107112
        msg = EmailMessage()
108113
        msg['Subject'] = self.filename
109114
        msg['From'] = self.conf["email"]

116121
            s.login(self.conf['user'], interface.askPassword())
117122
            s.send_message(msg)
118123
124+
    def save(self):
125+
        self.po.save()
126+
119127
    def content(self):
120-
        po = polib.pofile(self.popath)
121-
        return po
128+
        self.po = polib.pofile(self.popath)
129+
        po = [POEntry(x) for x in self.po]
130+
        return {'default': po}

offlate/systems/transifex.py unknown status 1

1+
import requests
2+
import json
3+
from ruamel import yaml
4+
import os
5+
6+
from requests.auth import HTTPBasicAuth
7+
8+
from pathlib import Path
9+
10+
from .entry import JSONEntry
11+
12+
def yaml_rec_load(path, source, dest):
13+
    ans = []
14+
    for s in source:
15+
        path2 = list(path)
16+
        path2.append(s)
17+
        if isinstance(source[s], str):
18+
            ans.append({'path': path, 'id': s, 'source_string': source[s], 'translation': dest[s]})
19+
        else:
20+
            ans.extend(yaml_rec_load(path2, source[s], dest[s]))
21+
    return ans
22+
23+
class TransifexProject:
24+
    def __init__(self, conf, name, lang, data = {}):
25+
        self.uri = "https://www.transifex.com"
26+
        self.conf = conf
27+
        self.name = name
28+
        self.lang = lang
29+
        self.data = data
30+
        self.basedir = ''
31+
        self.contents = {}
32+
33+
    def open(self, basedir):
34+
        self.basedir = basedir
35+
        with open(self.basedir + '/project.info') as f:
36+
            self.files = json.load(f)
37+
        self.slugs = [x['slug'] for x in self.files]
38+
        self.loadContent()
39+
40+
    def initialize(self, basedir):
41+
        self.basedir = basedir
42+
        self.updateFileList()
43+
        with open(self.basedir + '/project.info', 'w') as f:
44+
            f.write(json.dumps(self.files))
45+
        for slug in self.slugs:
46+
            self.getFile(slug)
47+
        self.loadContent()
48+
49+
    def update(self, callback):
50+
        self.updateFileList()
51+
52+
    def updateFileList(self):
53+
        self.files = []
54+
        self.slugs = []
55+
        ans = requests.get('https://api.transifex.com/organizations/'+
56+
                self.data['organization']+'/projects/'+self.name+
57+
                '/resources/?language_code='+self.lang,
58+
                auth=HTTPBasicAuth('api', self.conf['token']))
59+
        if ans.status_code == 200:
60+
            l = json.loads(ans.text)
61+
            self.slugs = [x['slug'] for x in l]
62+
            self.files = l
63+
    
64+
    def loadContent(self):
65+
        for ff in self.files:
66+
            with open(self.filename(ff['slug'], True)) as f:
67+
                with open(self.filename(ff['slug'], False)) as f2:
68+
                    if ff['i18n_type'] == 'YML':
69+
                        source = yaml.safe_load(f)
70+
                        dest = yaml.safe_load(f2)
71+
                        lang1 = list(source.keys())[0]
72+
                        lang2 = list(dest.keys())[0]
73+
                        self.contents[ff['slug']] = \
74+
                            yaml_rec_load([lang2], source[lang1], dest[lang2])
75+
76+
    def getFile(self, slug):
77+
        ans = requests.get('https://www.transifex.com/api/2/project/'+
78+
                self.name+'/resource/'+slug+'/content',
79+
                auth=HTTPBasicAuth('api', self.conf['token']))
80+
        if ans.status_code == 200:
81+
            with open(self.filename(slug, True), 'w') as f:
82+
                f.write(json.loads(ans.text)['content'])
83+
84+
        ans = requests.get('https://www.transifex.com/api/2/project/'+self.name+
85+
                '/resource/'+slug+'/translation/'+self.lang+'/?mode=translator',
86+
                auth=HTTPBasicAuth('api', self.conf['token']))
87+
        if ans.status_code == 200:
88+
            with open(self.filename(slug, False), 'w') as f:
89+
                f.write(json.loads(ans.text)['content'])
90+
        else:
91+
            print(ans.text)
92+
93+
    def filename(self, slug, is_source):
94+
        ext = ''
95+
        for ff in self.files:
96+
            if ff['slug'] == slug:
97+
                f = ff
98+
                break
99+
        if f['i18n_type'] == 'YML':
100+
            ext = 'yml'
101+
        return self.basedir + '/' + slug + ('.source' if is_source else '') + '.' + ext
102+
103+
    def send(self, interface):
104+
        self.save()
105+
        for ff in self.files:
106+
            print('{} => {}'.format(ff['slug'], ff['i18n_type']))
107+
            with open(self.filename(ff['slug'], False)) as f:
108+
                content = f.read()
109+
                sendcontent = {"content": content}
110+
                ans = requests.put('https://www.transifex.com/api/2/project/'+
111+
                        self.name+'/resource/'+ff['slug']+'/translation/'+self.lang+'/',
112+
                        json=sendcontent, auth=HTTPBasicAuth('api', self.conf['token']))
113+
                print(ans)
114+
                print(ans.text)
115+
        #for slug in self.slugs:
116+
        #    with open(self.filename(slug)) as f:
117+
        #        content = json.load(f)
118+
        #        sendcontent = {}
119+
        #        for s in content:
120+
        #            sendcontent[s['source_string']] = s['translation']
121+
        #        sendcontent = {'content': json.dumps({'content': sendcontent})}
122+
        #        ans = requests.put('https://www.transifex.com/api/2/project/'+
123+
        #                self.name+'/resource/'+slug+'/translation/'+self.lang+'/',
124+
        #                json=sendcontent, auth=HTTPBasicAuth('api', self.conf['token']))
125+
        #        print(ans)
126+
        #        print(ans.text)
127+
128+
    def save(self):
129+
        for slug in self.slugs:
130+
            data = {}
131+
            for d in self.contents[slug]:
132+
                path = d['path']
133+
                curr = data
134+
                for p in path:
135+
                    if p in curr:
136+
                        curr = curr[p]
137+
                    else:
138+
                        curr[p] = {}
139+
                        curr = curr[p]
140+
                curr[d['id']] = d['translation']
141+
            with open(self.filename(slug, False), 'w') as f:
142+
                yaml.dump(data, f, Dumper=yaml.RoundTripDumper, allow_unicode=True)
143+
144+
    def content(self):
145+
        contents = {}
146+
        for content in self.contents:
147+
            contents[content] = [JSONEntry(x) for x in self.contents[content]]
148+
        return contents

offlate/window.py

2121
2222
    def askPassword(self):
2323
        self.qd = QInputDialog()
24+
        self.qd.setLabelText(self.qd.tr("Please enter your password:"))
2425
        self.qd.setTextEchoMode(QLineEdit.Password)
2526
        self.qd.accepted.connect(self.ok)
2627
        self.qd.exec_()

3132
        super(ProjectView, self).__init__(parent)
3233
        self.project = project
3334
        self.content = self.project.content()
35+
        self.currentContent = list(self.content.keys())[0]
3436
        self.showTranslated = showTranslated
3537
        self.showUntranslated = showUntranslated
3638
        self.showFuzzy = showFuzzy

3941
    def updateContent(self):
4042
        self.treeWidget.clear()
4143
        items = []
42-
        for entry in self.content:
43-
            if entry.obsolete:
44+
        for entry in self.content[self.currentContent]:
45+
            if entry.isObsolete():
4446
                continue
4547
            cont = False
46-
            if self.showTranslated and entry.translated():
48+
            if self.showTranslated and entry.isTranslated():
4749
                cont = True
48-
            if self.showUntranslated and not entry.translated():
50+
            if self.showUntranslated and not entry.isTranslated():
4951
                cont = True
50-
            if self.showFuzzy and 'fuzzy' in entry.flags:
52+
            if self.showFuzzy and entry.isFuzzy():
5153
                cont = True
5254
            if not cont:
5355
                continue
54-
            msgstr = entry.msgstr
55-
            if 0 in entry.msgstr_plural:
56-
                msgstr = entry.msgstr_plural[0]
57-
            item = QTreeWidgetItem([entry.msgid, msgstr])
56+
            item = QTreeWidgetItem([entry.msgids[0], entry.msgstrs[0]])
5857
            item.setData(0, Qt.UserRole, entry)
5958
            items.append(item)
6059
        self.treeWidget.insertTopLevelItems(0, items)

6867
        self.msgid = QTextEdit()
6968
        self.msgid.setReadOnly(True)
7069
        self.msgstr = QTextEdit()
70+
        self.filechooser = QComboBox()
71+
        for project in list(self.content.keys()):
72+
            self.filechooser.addItem(project)
73+
        self.filechooser.currentIndexChanged.connect(self.changefile)
74+
75+
        if self.filechooser.count() > 1:
76+
            vbox.addWidget(self.filechooser)
7177
7278
        self.updateContent()
7379
        vbox.addWidget(self.treeWidget, 4)

7985
        self.treeWidget.setColumnWidth(0, size.width()/2)
8086
        self.treeWidget.currentItemChanged.connect(self.selectItem)
8187
88+
    def changefile(self):
89+
        self.currentContent = list(self.content.keys())[self.filechooser.currentIndex()]
90+
        self.updateContent()
91+
8292
    def selectItem(self, current, old):
8393
        if current == None:
8494
            return

8797
        self.hbox.removeWidget(self.msgstr)
8898
        self.msgid.deleteLater()
8999
        self.msgstr.deleteLater()
90-
        if 0 in data.msgstr_plural:
100+
101+
        if len(data.msgstrs) > 1:
91102
            self.msgid = QTabWidget();
92103
            self.msgstr = QTabWidget();
93104
            singular = QTextEdit()
94105
            singular.setReadOnly(True)
95-
            singular.setText(data.msgid)
106+
            singular.setText(data.msgids[0])
96107
            plural = QTextEdit()
97108
            plural.setReadOnly(True)
98-
            plural.setText(data.msgid_plural)
109+
            plural.setText(data.msgids[1])
99110
            self.msgid.addTab(singular, self.tr("Singular"))
100111
            self.msgid.addTab(plural, self.tr("Plural"))
101112
            i = 0
102-
            while i in data.msgstr_plural:
113+
            for msgstr in data.msgstrs:
103114
                form = QTextEdit()
104-
                form.setText(data.msgstr_plural[i])
115+
                form.setText(msgstr)
105116
                form.textChanged.connect(self.modify)
106117
                self.msgstr.addTab(form, str(i))
107118
                i=i+1

109120
            self.msgid = QTextEdit()
110121
            self.msgid.setReadOnly(True)
111122
            self.msgstr = QTextEdit()
112-
            self.msgid.setText(data.msgid)
113-
            self.msgstr.setText(data.msgstr)
123+
            self.msgid.setText(data.msgids[0])
124+
            self.msgstr.setText(data.msgstrs[0])
114125
            self.msgstr.textChanged.connect(self.modify)
115126
        self.hbox.addWidget(self.msgid)
116127
        self.hbox.addWidget(self.msgstr)

119130
        item = self.treeWidget.currentItem()
120131
        data = item.data(0, Qt.UserRole)
121132
        if self.msgstr.__class__.__name__ == "QTextEdit":
122-
            data.msgstr = self.msgstr.toPlainText()
123-
            item.setText(1, data.msgstr)
133+
            msgstr = self.msgstr.toPlainText()
134+
            data.update(0, msgstr)
135+
            item.setText(1, msgstr)
124136
        else:
125137
            i = 0
126-
            while i in data.msgstr_plural:
127-
                data.msgstr_plural[i] = self.msgstr.widget(i).toPlainText()
138+
            for msgstr in data.msgstrs:
139+
                data.update(i, self.msgstr.widget(i).toPlainText())
128140
                i=i+1
129141
            item.setText(1, data.msgstr_plural[0])
130142
131143
    def save(self):
132-
        self.content.save()
144+
        self.project.save()
133145
134146
    def send(self):
135-
        self.content.save()
147+
        self.project.save()
136148
        self.project.send(Interface())
137149
138150
    def askmerge(self, msgid, oldstr, newstr):

140152
        return newstr
141153
142154
    def update(self):
143-
        self.content.save()
155+
        self.project.save()
144156
        self.project.update(self.askmerge)
145157
        self.content = self.project.content()
146158
        self.updateContent()

176188
        predefinedbox.addWidget(self.predefinedprojects)
177189
178190
        contentbox = QVBoxLayout()
191+
        formbox = QGroupBox(self.tr("Project information"))
192+
        self.formLayout = QFormLayout()
193+
        formbox.setLayout(self.formLayout)
194+
179195
        self.nameWidget = QLineEdit()
180196
        self.langWidget = QLineEdit()
181-
        contentbox.addWidget(self.nameWidget)
182-
        contentbox.addWidget(self.langWidget)
197+
        self.formLayout.addRow(QLabel(self.tr("Name:")), self.nameWidget)
198+
        self.formLayout.addRow(QLabel(self.tr("Target Language:")), self.langWidget)
183199
        self.combo = QComboBox()
184200
        self.combo.addItem(self.tr("The Translation Project"))
185-
        contentbox.addWidget(self.combo)
201+
        self.combo.addItem(self.tr("Transifex"))
202+
        self.formLayout.addRow(self.combo)
203+
186204
        hhbox = QHBoxLayout()
187205
        cancel = QPushButton(self.tr("Cancel"))
188206
        ok = QPushButton(self.tr("OK"))
189207
        hhbox.addWidget(cancel)
190208
        hhbox.addWidget(ok)
209+
        contentbox.addWidget(formbox)
191210
        contentbox.addLayout(hhbox)
192211
        hbox.addLayout(predefinedbox)
193212
        hbox.addLayout(contentbox)
194213
214+
        self.additionalFields = []
215+
        self.additionalFields.append([])
216+
        self.additionalFields.append([])
217+
        self.transifexOrganisation = QLineEdit()
218+
        transifexOrganisationLabel = QLabel(self.tr("Organization"))
219+
        self.additionalFields[1].append({'label': transifexOrganisationLabel,
220+
            'widget': self.transifexOrganisation})
221+
195222
        self.setLayout(hbox)
196223
197224
        self.predefinedprojects.currentItemChanged.connect(self.fill)
198225
        cancel.clicked.connect(self.close)
199226
        ok.clicked.connect(self.ok)
200227
        self.searchfield.textChanged.connect(self.filter)
228+
        self.combo.currentIndexChanged.connect(self.othersystem)
201229
202230
    def ok(self):
203231
        self.askNew = True

208236
        data = item.data(Qt.UserRole)
209237
        self.nameWidget.setText(data['name'])
210238
        self.combo.setCurrentIndex(int(data['system']))
239+
        if data['system'] == 1:
240+
            self.transifexOrganisation.setText(data['organisation'])
211241
212242
    def filter(self):
213243
        search = self.searchfield.text()

231261
    def getProjectSystem(self):
232262
        return self.combo.currentIndex()
233263
264+
    def getProjectInfo(self):
265+
        if self.getProjectSystem() == 0:
266+
            return {}
267+
        if self.getProjectSystem() == 1:
268+
            return {'organization': self.additionalFields[1][0]['widget'].text()}
269+
        return {}
270+
271+
    def othersystem(self):
272+
        for system in self.additionalFields:
273+
            for widget in system:
274+
                self.formLayout.takeRow(widget['widget'])
275+
                widget['widget'].hide()
276+
                widget['label'].hide()
277+
        self.formLayout.invalidate()
278+
        for widget in self.additionalFields[self.combo.currentIndex()]:
279+
            self.formLayout.addRow(widget['label'], widget['widget'])
280+
            widget['widget'].show()
281+
            widget['label'].show()
282+
234283
class SettingsWindow(QDialog):
235284
    def __init__(self, preferences, parent = None):
236285
        super().__init__(parent)

352401
        if not w.wantNew():
353402
            return
354403
        self.manager.createProject(w.getProjectName(), w.getProjectLang(),
355-
                    w.getProjectSystem())
404+
                    w.getProjectSystem(), w.getProjectInfo())
356405
        self.open(w.getProjectName())
357406
358407
    def send(self):

setup.py

55
    version="0.1.dev",
66
    packages=find_packages(exclude=['.guix-profile*']),
77
    python_requires = '>=3',
8-
    install_requires=['polib'],
8+
    install_requires=['polib', 'PyYAML'],
99
    entry_points={
1010
        'gui_scripts': [
1111
            'offlate=offlate.window:main',