1#!/usr/bin/python3 2 3from gi.repository import Gtk 4import tokenize 5import token 6import re 7 8def match_ignore(string): 9 if string.startswith("'''"): 10 return True # ignore docstring etc. 11 12 string = string.strip('\'"') 13 if not re.search(r'[a-zA-Z]', string): 14 return True # ignore without any letter 15 elif len(string) < 3: 16 return True # ignore 'w' etc. 17 elif re.match(r'^</?\w+>$', string): 18 return True # ignore open / close XML tags 19 elif string.startswith('zim.'): 20 return True # module names 21 elif string.startswith('BUG'): 22 return True # assertion (?) 23 elif string.startswith('TODO'): 24 return True # assertion (?) 25 26 return False 27 28ignore_functions = ('setdefault', 'connect', 'connect_after', 'connect_object', 'get_property', 'emit', 'info', 'debug', 'warn', 'exception', 'get_action') 29 30 31class Internationalizer(Gtk.Window): 32 33 def __init__(self): 34 #~ def __init__(self, dir): 35 GObject.GObject.__init__(self) 36 self.set_title('Internationalizer') 37 self.set_default_size(500, 500) 38 #~ self.dir = dir 39 40 vbox = Gtk.VBox() 41 self.add(vbox) 42 43 self.status_label = Gtk.Label() 44 vbox.pack_start(self.status_label, False) 45 46 hbox = Gtk.HBox() 47 vbox.add(hbox) 48 scrollwindow = Gtk.ScrolledWindow() 49 hbox.add(scrollwindow) 50 51 self.textview = Gtk.TextView() 52 self.textview.set_left_margin(12) 53 self.textview.set_right_margin(5) 54 scrollwindow.add(self.textview) 55 56 bbox = Gtk.HButtonBox() 57 vbox.pack_start(bbox, False) 58 59 savebutton = Gtk.Button(stock='gtk-save') 60 savebutton.connect_object('clicked', self.__class__.save_file, self) 61 bbox.add(savebutton) 62 63 reloadbutton = Gtk.Button(stock='gtk-refresh') 64 reloadbutton.connect_object('clicked', self.__class__.reload_file, self) 65 bbox.add(reloadbutton) 66 67 nextbutton = Gtk.Button(stock='gtk-forward') 68 nextbutton.connect_object('clicked', self.__class__.next_tag, self) 69 bbox.add(nextbutton) 70 71 applybutton = Gtk.Button(stock='gtk-apply') 72 applybutton.connect_object('clicked', self.__class__.apply_mark, self) 73 bbox.add(applybutton) 74 75 76 def open_file(self, file): 77 if self.textview.get_buffer().get_modified(): 78 self.save_file() 79 self.file = file 80 buffer = Gtk.TextBuffer() 81 print('Reading %s' % self.file) 82 buffer.set_text(open(self.file).read()) 83 self.textview.set_buffer(buffer) 84 85 buffer.create_tag('translated', background='green') 86 buffer.create_tag('untranslated', background='red') 87 buffer.create_tag('notsure', background='orange') 88 89 translated, untranslated, notsure = self.tokenize() 90 self.status_label.set_text( 91 "%i translated, %i untranslated, %i not sure" 92 % (len(translated), len(untranslated), len(notsure)) 93 ) 94 95 def get_iter(coord): 96 row, col = coord 97 row -= 1 98 iter = buffer.get_iter_at_line(row) 99 iter.forward_chars(col) 100 return iter 101 102 for start, end in translated: 103 start, end = list(map(get_iter, (start, end))) 104 buffer.apply_tag_by_name('translated', start, end) 105 106 for start, end in untranslated: 107 start, end = list(map(get_iter, (start, end))) 108 buffer.apply_tag_by_name('untranslated', start, end) 109 110 for start, end in notsure: 111 start, end = list(map(get_iter, (start, end))) 112 buffer.apply_tag_by_name('notsure', start, end) 113 114 buffer.place_cursor(buffer.get_iter_at_offset(0)) 115 116 def tokenize(self): 117 translated = [] 118 untranslated = [] 119 notsure = [] 120 tokens = tokenize.generate_tokens(open(self.file).readline) 121 122 reset = lambda: {'funcname': None, 'isfunc': False, 'iskey': False} 123 state = reset() 124 125 for type, string, start, end, line in tokens: 126 if type == token.STRING: 127 if state['isfunc'] and state['funcname'] == '_': 128 translated.append((start, end)) 129 elif state['iskey'] or \ 130 (state['isfunc'] and state['funcname'] in ignore_functions) or \ 131 match_ignore(string): 132 notsure.append((start, end)) 133 else: 134 untranslated.append((start, end)) 135 state = reset() 136 elif type == token.NAME: 137 state = reset() 138 state['funcname'] = string 139 elif type == token.OP and string == '(': 140 state['iskey'] = False 141 state['isfunc'] = True 142 elif type == token.OP and string == '[': 143 state['isfunc'] = False 144 state['iskey'] = True 145 else: 146 state = reset() 147 148 return translated, untranslated, notsure 149 150 def save_file(self): 151 buffer = self.textview.get_buffer() 152 content = buffer.get_text(*buffer.get_bounds()) 153 print('Writing %s' % self.file) 154 open(self.file, 'w').write(content) 155 156 def reload_file(self): 157 self.open_file(self.file) 158 159 def next_tag(self): 160 '''Select the next untranslated string''' 161 buffer = self.textview.get_buffer() 162 iter = buffer.get_iter_at_mark(buffer.get_insert()) 163 tag = buffer.get_tag_table().lookup('untranslated') 164 iter.forward_to_tag_toggle(tag) 165 if not iter.begins_tag(tag): 166 iter.forward_to_tag_toggle(tag) 167 bound = iter.copy() 168 bound.forward_to_tag_toggle(tag) 169 buffer.select_range(iter, bound) 170 self.textview.scroll_mark_onscreen(buffer.get_selection_bound()) 171 self.textview.scroll_mark_onscreen(buffer.get_insert()) 172 173 def apply_mark(self): 174 '''Wrap current selected string in "_( .. )"''' 175 buffer = self.textview.get_buffer() 176 bounds = buffer.get_selection_bounds() 177 if bounds: 178 start, end = bounds 179 else: 180 iter = buffer.get_iter_at_mark(buffer.get_insert()) 181 for tag in 'untranslated', 'notsure': 182 tag = buffer.get_tag_table().lookup(tag) 183 if iter.has_tag(tag): 184 iter.backward_to_tag_toggle(tag) 185 bound = iter.copy() 186 bound.forward_to_tag_toggle(tag) 187 start, end = iter, bound 188 break 189 else: 190 return 191 192 buffer.remove_all_tags(start, end) 193 buffer.apply_tag_by_name('translated', start, end) 194 start, end = start.get_offset(), end.get_offset() 195 if start > end: 196 start, end = end, start 197 buffer.insert(buffer.get_iter_at_offset(end), ')') 198 buffer.insert(buffer.get_iter_at_offset(start), '_(') 199 self.next_tag() 200 201if __name__ == '__main__': 202 import sys 203 app = Internationalizer() 204 app.open_file(sys.argv[1]) 205 app.show_all() 206 app.connect('destroy', lambda o: Gtk.main_quit()) 207 Gtk.main() 208