Removing again windows' long_locales! :p
[blender.git] / release / scripts / modules / bl_i18n_utils / update_languages_menu.py
1 #!/usr/bin/python3
2
3 # ***** BEGIN GPL LICENSE BLOCK *****
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #
19 # ***** END GPL LICENSE BLOCK *****
20
21 # <pep8 compliant>
22
23 # Update "languages" text file used by Blender at runtime to build translations menu.
24
25 import os
26 import sys
27 import shutil
28
29 try:
30     import settings
31     import utils
32 except:
33     from . import (settings, utils)
34
35 TRUNK_PO_DIR = settings.TRUNK_PO_DIR
36 TRUNK_MO_DIR = settings.TRUNK_MO_DIR
37
38 LANGUAGES_CATEGORIES = settings.LANGUAGES_CATEGORIES
39 LANGUAGES = settings.LANGUAGES
40 LANGUAGES_FILE = settings.LANGUAGES_FILE
41
42 OK = 0
43 MISSING = 1
44 TOOLOW = 2
45 FORBIDDEN = 3
46 FLAG_MESSAGES = {
47     OK: "",
48     MISSING: "No translation yet!",
49     TOOLOW: "Not enough advanced to be included...",
50     FORBIDDEN: "Explicitly forbidden!",
51 }
52
53 def find_matching_po(languages, stats, forbidden):
54     """Match languages defined in LANGUAGES setting to relevant po, if possible!"""
55     ret = []
56     for uid, label, org_key, in languages:
57         key = org_key
58         if key not in stats:
59             # Try to simplify the key (eg from es_ES to es).
60             if '_' in org_key:
61                 key = org_key[0:org_key.index('_')]
62             # For stuff like sr_SR@latin -> sr@latin...
63             if '@' in org_key:
64                 key = key + org_key[org_key.index('@'):]
65         if key in stats:
66             if key in forbidden:
67                 ret.append((stats[key], uid, label, org_key, FORBIDDEN))
68             else:
69                 ret.append((stats[key], uid, label, org_key, OK))
70         else:
71             ret.append((0.0, uid, label, org_key, MISSING))
72     return ret
73
74 def main():
75     import argparse
76     parser = argparse.ArgumentParser(description=""
77                         "Update 'languages' text file used by Blender at runtime to build translations menu.")
78     parser.add_argument('-m', '--min_translation', type=int, default=-100,
79                         help="Minimum level of translation, as a percentage "
80                              "(translations below this are commented out in menu).")
81     parser.add_argument('langs', metavar='ISO_code', nargs='*',
82                         help="Unconditionally exclude those languages from the menu.")
83     args = parser.parse_args()
84
85     ret = 0
86     min_trans = args.min_translation / 100.0
87     forbidden = set(args.langs)
88     # 'DEFAULT' and en_US are always valid, fully-translated "languages"!
89     stats = {"DEFAULT": 1.0, "en_US": 1.0}
90
91     # Get the "done level" of each po in trunk...
92     for po in os.listdir(TRUNK_PO_DIR):
93         if po.endswith(".po") and not po.endswith("_raw.po"):
94             lang = os.path.basename(po)[:-3]
95             u1, u2, _stats = utils.parse_messages(os.path.join(TRUNK_PO_DIR, po))
96             stats[lang] = _stats["trans_msg"] / _stats["tot_msg"]
97
98     # Generate languages file used by Blender's i18n system.
99     # First, match all entries in LANGUAGES to a lang in stats, if possible!
100     stats = find_matching_po(LANGUAGES, stats, forbidden)
101     limits = sorted(LANGUAGES_CATEGORIES, key=lambda it: it[0], reverse=True)
102     idx = 0
103     stats = sorted(stats, key=lambda it: it[0], reverse=True)
104     langs_cats = [[] for i in range(len(limits))]
105     highest_uid = 0
106     for prop, uid, label, key, flag in stats:
107         if prop < limits[idx][0]:
108             # Sub-sort languages by iso-codes.
109             langs_cats[idx].sort(key=lambda it: it[2])
110             idx += 1
111         if prop < min_trans and flag == OK:
112             flag = TOOLOW
113         langs_cats[idx].append((uid, label, key, flag))
114         if abs(uid) > highest_uid:
115             highest_uid = abs(uid)
116     # Sub-sort last group of languages by iso-codes!
117     langs_cats[idx].sort(key=lambda it: it[2])
118     with open(os.path.join(TRUNK_MO_DIR, LANGUAGES_FILE), 'w', encoding="utf-8") as f:
119         f.write("# File used by Blender to know which languages (translations) are available, \n")
120         f.write("# and to generate translation menu.\n")
121         f.write("#\n")
122         f.write("# File format:\n")
123         f.write("# ID:MENULABEL:ISOCODE\n")
124         f.write("# ID must be unique, except for 0 value (marks categories for menu).\n")
125         f.write("# Line starting with a # are comments!\n")
126         f.write("#\n")
127         f.write("# Automatically generated by bl_i18n_utils/update_languages_menu.py script.\n")
128         f.write("# Highest ID currently in use: {}\n".format(highest_uid))
129         for cat, langs_cat in zip(limits, langs_cats):
130             f.write("#\n")
131             # Write "category menu label"...
132             if langs_cat:
133                 f.write("0:{}::\n".format(cat[1]))
134             else:
135                 # Do not write the category if it has no language!
136                 f.write("# Void category! #0:{}:\n".format(cat[1]))
137             # ...and all matching language entries!
138             for uid, label, key, flag in langs_cat:
139                 if flag == OK:
140                     f.write("{}:{}:{}\n".format(uid, label, key))
141                 else:
142                     # Non-existing, commented entry!
143                     f.write("# {} #{}:{}:{}\n".format(FLAG_MESSAGES[flag], uid, label, key))
144
145
146 if __name__ == "__main__":
147     print("\n\n *** Running {} *** \n".format(__file__))
148     sys.exit(main())