Skip to content

Commit 7286d2d

Browse files
authored
Merge pull request #1 from ipcjs/master
update
2 parents 705a61e + 11ec5b3 commit 7286d2d

14 files changed

Lines changed: 374 additions & 123 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.venv/
22
tmp/
33
__pycache__/
4-
*.pyc
4+
*.pyc
5+
*.stackdump

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,23 @@
99
"type": "python",
1010
"request": "launch",
1111
"program": "${workspaceFolder}/tg.py",
12+
"env": {
13+
"https_proxy": "http://127.0.0.1:1080",
14+
"http_proxy": "http://127.0.0.1:1080",
15+
},
1216
"console": "integratedTerminal"
1317
},
18+
{
19+
"name": "selpic.py",
20+
"type": "python",
21+
"request": "launch",
22+
"program": "${workspaceFolder}/selpic.py",
23+
"console": "integratedTerminal",
24+
"env": {
25+
"https_proxy": "http://127.0.0.1:1080",
26+
"http_proxy": "http://127.0.0.1:1080",
27+
},
28+
},
1429
{
1530
"name": "Python: Current File",
1631
"type": "python",

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,16 @@
33
1. export to xls
44
2. import from xls
55
3. translate by google
6+
7+
## How to set proxy?
8+
9+
```sh
10+
# replace to you proxy address
11+
export https_proxy=http://127.0.0.1:1080
12+
```
13+
14+
## Develop
15+
16+
```sh
17+
pipenv install
18+
```

att/__init__.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ def merge_items(base_items, new_items):
2828
key_name = ItemsUtil.get_key_name_from_items(new_items) # 可能为: key/ios_key/web_key
2929
for key, item in new_items.items():
3030
for base_item in base_items.values():
31-
if not base_item[key_name] and item.all_lang_equals(base_item):
31+
if not getattr(base_item, key_name) and item.all_lang_equals(base_item):
3232
# base_item不存在[key_name]的情况下, 若所有语言的翻译都相同, 说明是同一个字符串, 给base_item添加[key_name]
33-
base_item[key_name] = item[key_name]
33+
setattr(base_item, key_name, getattr(item, key_name))
3434
break
3535
else: # 没有找到三语相同的时, 需要添加item
3636
items[key] = item
@@ -85,18 +85,15 @@ def process_diff_cover(files, new_items):
8585
else:
8686
pass
8787

88-
def process_diff_all(files: typing.Tuple[File, ...], new_items: typing.Dict[str, Item], item_key_name: str):
88+
def process_diff_all(files: typing.Tuple[File, ...], new_items: typing.Dict[str, Item]):
8989
"""
9090
以new_items为准, 处理删除和新增
9191
9292
:param files:
9393
:param new_items:
94-
:param item_key_name: new_items的key使用的Item的哪个字段; 可选 'key', 'ios_key';
9594
:return:
9695
"""
9796
items = ItemsUtil.read_files_to_items(files)
98-
if item_key_name: # 过滤android或ios的item
99-
new_items = Dict((k, v) for k, v in new_items.items() if str(k) == v[item_key_name])
10097
lang_files = list(map(lambda f: (f.lang, f), files)) # (注意: 有可能存在多个file的lang相同的情况...)
10198
for key, item in items.items(): # 遍历items
10299
new_item = new_items[key]
@@ -151,7 +148,8 @@ def process_diff_all(files: typing.Tuple[File, ...], new_items: typing.Dict[str,
151148
# ItemsUtil.cover_items_to_files(ios_files, new_items)
152149

153150
for files in files_tuple:
154-
process_diff_all(files, new_items, ItemsUtil.get_key_name_from_files(files))
151+
key_class = ItemsUtil.get_key_class_from_files(files)
152+
process_diff_all(files, Dict({k: v for k, v in new_items.items() if isinstance(k, key_class)}))
155153
ItemsUtil.cover_items_to_files(files, new_items)
156154

157155

@@ -166,8 +164,9 @@ def process_translate(files: typing.Tuple[File, ...], items: typing.Dict[str, It
166164
has_translate_files = [(lang, f) for lang, f in main_lang_files if item[lang]]
167165
if len(has_translate_files) > 0 and len(has_translate_files) < len(main_lang_files):
168166
source = Dict(lang=has_translate_files[0][0], text=item[has_translate_files[0][0]]) # 将第一个有翻译的语言作为源语言
169-
for lang, file in main_lang_files:
170-
try:
167+
try:
168+
temp_result = Dict()
169+
for lang, file in main_lang_files:
171170
old_text = item[lang]
172171
# 因为存在将zh写在en的file里的情况, 故大多数情况下需要把所有的file都翻译一遍
173172
if translate_all_lang or not old_text:
@@ -178,12 +177,18 @@ def process_translate(files: typing.Tuple[File, ...], items: typing.Dict[str, It
178177
p('remind', '(translate %s => %s)%s:\n%s\n%s' % (source.lang, lang, key, old_text, new_text))
179178
replace = input('是否修改该字符串:(y)') in 'yY'
180179
if replace:
181-
item[lang] = new_text
182-
p('info', 'translate %s => %s >> %s => %s' % (source.lang, lang, source.text, item[lang]))
183-
if not old_text: # 给file添加空行, see: @add_empty_line_for_cover
184-
file.add(key, '')
185-
except Exception as e:
186-
p('warn', 'translate %s => %s >> %s =x %s' % (source.lang, lang, source.text, e))
180+
temp_result[lang] = (new_text, old_text, file)
181+
# 翻译结果放在temp_result中去, item的所有语言的翻译完全成功后再应用更改
182+
# 若中途报错, 则item的所有语言翻译都会回退
183+
# 这样做主要是为了防止source是zh写在en的file中时, 第一次是zh翻其他语言,
184+
# 第二次若source已经被翻成了en, 则是en翻其他语言, 会存在结果不一致的问题
185+
for lang, (new_text, old_text, file) in temp_result.items():
186+
item[lang] = new_text
187+
p('info', 'translate %s => %s >> %s => %s' % (source.lang, lang, source.text, item[lang]))
188+
if not old_text: # 给file添加空行, see: @add_empty_line_for_cover
189+
file.add(key, '')
190+
except Exception as e:
191+
p('warn', 'translate %s => ?? >> %s =x %s' % (source.lang, source.text, e))
187192

188193
items = ItemsUtil.read_files_to_items(files)
189194

att/files/file.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import typing
22
from ..item import *
3+
import re
4+
35

46
class File:
57
def __init__(self, file, lang, is_main=True, *, keyClass: typing.Type[Key]):
@@ -21,3 +23,28 @@ def to_file(self, file): pass
2123

2224
def save(self):
2325
self.to_file(self.file)
26+
27+
28+
# 除`\n`以外的`\`
29+
# @text_newline_sep
30+
REG_BACKSLASH = re.compile(r'\\(?!n)')
31+
32+
33+
def convert_string_to_text(string: str) -> str:
34+
# string中存在转义:
35+
# \' -> '
36+
# \" -> "
37+
# \\ -> \
38+
# \n -> 不处理, item中用`\n`表示换行
39+
# @text_newline_sep
40+
return string.replace("\\'", "'").replace('\\"', '"').replace('\\\\', '\\')
41+
42+
43+
def convert_text_to_string(text: str, replace_single_quote=True, replace_double_quote=True) -> str:
44+
# 同上, 反向转换
45+
r = re.sub(REG_BACKSLASH, '\\\\', text)
46+
if replace_single_quote:
47+
r = r.replace("'", "\\'")
48+
if replace_double_quote:
49+
r = r.replace('"', '\\"')
50+
return r

att/files/json_file.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class JsonFile(File):
88
@staticmethod
99
def read(file):
1010
if not os.path.exists(file):
11+
p('warn', '%(file)s no exists.' % {'file': file})
1112
return {}
1213
obj = {}
1314
with open(file, mode='r', encoding='utf-8') as f:
@@ -75,5 +76,5 @@ def to_items(self, items):
7576
def to_file(self, file):
7677
if not os.path.exists(file):
7778
os.makedirs(os.path.dirname(file), exist_ok=True)
78-
with open(file, mode='w', encoding='utf-8', newline='\n') as f:
79+
with open(file, mode='w', encoding='utf-8', newline=None) as f:
7980
json.dump(self.dict_to_obj(self._dict), f, indent=2, ensure_ascii=False)

att/files/string_file.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
from .file import *
66

7+
78
class Line:
89
@staticmethod
910
def create(text):
@@ -34,13 +35,22 @@ def __init__(self, text, key, value):
3435
self.key = key
3536
self.value = value
3637

38+
def get_normalized_value(self):
39+
return convert_string_to_text(self.value)
40+
41+
def set_normalized_value(self, value):
42+
# iOS不需要替换单引号
43+
self.value = convert_text_to_string(value, replace_single_quote=False)
44+
3745
def text(self):
3846
return KeyValueLine.TEXT_FORMAT.format(key=self.key, value=self.value)
3947

48+
4049
class StringsFile(File):
4150
@staticmethod
4251
def read(file):
4352
if not os.path.exists(file):
53+
p('warn', '%(file)s no exists.' % {'file': file})
4454
return []
4555
lines = []
4656
with open(file, mode='r', encoding='utf-8') as f:
@@ -68,13 +78,13 @@ def cover(self, items):
6878
key = self.keyClass(line.key)
6979
item = items[key]
7080
if item:
71-
old_text = line.value
81+
old_text = line.get_normalized_value()
7282
new_text = item[self.lang]
7383
if new_text is None:
7484
# @item_lang_is_none
7585
p('skip', ' [%(lang)s] %(key)s: new_text is None' % {'key': key, 'lang': self.lang})
7686
elif old_text != new_text:
77-
line.value = new_text
87+
line.set_normalized_value(new_text)
7888

7989
def to_items(self, items):
8090
for line in filter(lambda l: isinstance(l, KeyValueLine), self._lines):
@@ -87,14 +97,14 @@ def to_items(self, items):
8797
p('warn', '存在同名key: %(key)s [%(old_value)s=>%(new_value)s] (%(lang)s)' % {
8898
'key': key,
8999
'old_value': item[self.lang],
90-
'new_value': line.value,
100+
'new_value': line.get_normalized_value(),
91101
'lang': self.lang
92102
})
93-
item[self.lang] = line.value
103+
item[self.lang] = line.get_normalized_value()
94104

95105
def to_file(self, file):
96106
if not os.path.exists(file):
97107
os.makedirs(os.path.dirname(file), exist_ok=True)
98-
with open(file, mode='w', encoding='utf-8', newline='\n') as f:
108+
with open(file, mode='w', encoding='utf-8', newline=None) as f:
99109
for line in self._lines:
100110
f.writelines(line.text())

att/files/xml_file.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from ..item import *
66
from .file import *
77

8-
REMIND_WHEN_ESCAPE = True
98
REG_QUOTE_TEXT = re.compile(r'^"(?P<content>.*?)\s*"$')
109
REG_REF_STRING_TEXT = re.compile(r'@string/\w+')
1110

@@ -14,6 +13,7 @@ class XmlFile(File):
1413
@staticmethod
1514
def read(file):
1615
if not os.path.exists(file):
16+
p('warn', '%(file)s no exists.' % {'file': file})
1717
return dom.parseString('<resources>\n</resources>')
1818
root = dom.parse(file)
1919
resources_node = root.getElementsByTagName('resources')[0]
@@ -69,7 +69,7 @@ def cover(self, items):
6969
if text_node:
7070
item = items[key]
7171
if item:
72-
old_text = text_node.data
72+
old_text = NodeUtil.get_text_in_text_node(text_node)
7373
new_text = item[self.lang]
7474
# 双引号括住的字符串, 需要特殊处理
7575
match = REG_QUOTE_TEXT.fullmatch(old_text)
@@ -81,22 +81,8 @@ def cover(self, items):
8181
elif old_text == new_text: # 文本相同, 不保存
8282
pass
8383
else:
84-
# `'`和`\`需要转义
85-
if "'" in old_text or '\\' in old_text \
86-
or "'" in new_text or '\\' in new_text:
87-
# 先替换`\`=>`\\`, 再替换`'`=>`\'`
88-
new_text = new_text.replace('\\', '\\\\').replace("'", "\\'")
89-
if new_text != old_text:
90-
if REMIND_WHEN_ESCAPE:
91-
p('remind', '%s:\n%s\n%s' % (key, old_text, new_text))
92-
if input('是否修改该字符串:(y)') not in 'yY': # ''/'y'/'Y'表示'是'
93-
continue # 不保存
94-
else:
95-
p('warn', key, old_text, '=>', new_text)
96-
else:
97-
continue # 文本相同, 跳过保存
9884
# 保存
99-
text_node.data = new_text
85+
NodeUtil.set_text_in_text_node(text_node, new_text)
10086

10187
def to_items(self, items):
10288
string_nodes = self._dom.getElementsByTagName('string')
@@ -110,7 +96,7 @@ def to_items(self, items):
11096
untranslatable=node.getAttribute('translatable') == 'false',
11197
auto_translate=not (node.getAttribute('translateAuto') == 'false'))
11298
items[key] = item
113-
item[self.lang] = text_node.data
99+
item[self.lang] = NodeUtil.get_text_in_text_node(text_node)
114100

115101
def to_file(self, file):
116102
str_io = io.StringIO()
@@ -121,7 +107,7 @@ def to_file(self, file):
121107
content = str_io.getvalue().replace('&quot;', '"').replace('&gt;', '>')
122108
if not os.path.exists(file): # 保证目录存在, 方便之后创建文件
123109
os.makedirs(os.path.dirname(file), exist_ok=True)
124-
with open(file, mode='w', encoding='utf-8', newline='\n') as w:
110+
with open(file, mode='w', encoding='utf-8', newline=None) as w:
125111
w.write(content)
126112

127113

@@ -139,6 +125,15 @@ def get_text_node_in_string(node, log=False):
139125
})
140126
return None
141127

128+
@staticmethod
129+
def get_text_in_text_node(node) -> str:
130+
return convert_string_to_text(node.data)
131+
132+
@staticmethod
133+
def set_text_in_text_node(node, text):
134+
# xml中双引号不需要转义
135+
node.data = convert_text_to_string(text, replace_double_quote=False)
136+
142137
@staticmethod
143138
def to_text(node: dom.Node) -> str:
144139
if node.nodeType == node.TEXT_NODE:

0 commit comments

Comments
 (0)