rename module to something less generic.
[blender.git] / release / scripts / modules / bl_i18n_utils / merge_po.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 # Merge one or more .po files into the first dest one.
24 # If a msgkey is present in more than one merged po, the one in the first file wins, unless 
25 # it’s marked as fuzzy and one later is not.
26 # The fuzzy flag is removed if necessary.
27 # All other comments are never modified.
28 # However, commented messages in dst will always remain commented, and commented messages are
29 # never merged from sources.
30
31 import sys
32 from codecs import open
33
34 import utils
35
36
37 def main():
38     import argparse
39     parser = argparse.ArgumentParser(description="" \
40                     "Merge one or more .po files into the first dest one.\n" \
41                     "If a msgkey (msgid, msgctxt) is present in more than " \
42                     "one merged po, the one in the first file wins, unless " \
43                     "it’s marked as fuzzy and one later is not.\n" \
44                     "The fuzzy flag is removed if necessary.\n" \
45                     "All other comments are never modified.\n" \
46                     "Commented messages in dst will always remain " \
47                     "commented, and commented messages are never merged " \
48                     "from sources.")
49     parser.add_argument('-s', '--stats', action="store_true",
50                         help="Show statistics info.")
51     parser.add_argument('-r', '--replace', action="store_true",
52                         help="Replace existing messages of same \"level\" already in dest po.")
53     parser.add_argument('dst', metavar='dst.po',
54                         help="The dest po into which merge the others.")
55     parser.add_argument('src', metavar='src.po', nargs='+',
56                         help="The po's to merge into the dst.po one.")
57     args = parser.parse_args()
58
59
60     ret = 0
61     done_msgkeys = set()
62     done_fuzzy_msgkeys = set()
63     nbr_merged = 0
64     nbr_replaced = 0
65     nbr_added = 0
66     nbr_unfuzzied = 0
67
68     dst_messages, dst_states, dst_stats = utils.parse_messages(args.dst)
69     if dst_states["is_broken"]:
70         print("Dest po is BROKEN, aborting.")
71         return 1
72     if args.stats:
73         print("Dest po, before merging:")
74         utils.print_stats(dst_stats, prefix="\t")
75     # If we don’t want to replace existing valid translations, pre-populate
76     # done_msgkeys and done_fuzzy_msgkeys.
77     if not args.replace:
78         done_msgkeys =  dst_states["trans_msg"].copy()
79         done_fuzzy_msgkeys = dst_states["fuzzy_msg"].copy()
80     for po in args.src:
81         messages, states, stats = utils.parse_messages(po)
82         if states["is_broken"]:
83             print("\tSrc po {} is BROKEN, skipping.".format(po))
84             ret = 1
85             continue
86         print("\tMerging {}...".format(po))
87         if args.stats:
88             print("\t\tMerged po stats:")
89             utils.print_stats(stats, prefix="\t\t\t")
90         for msgkey, val in messages.items():
91             msgctxt, msgid = msgkey
92             # This msgkey has already been completely merged, or is a commented one,
93             # or the new message is commented, skip it.
94             if msgkey in (done_msgkeys | dst_states["comm_msg"] | states["comm_msg"]):
95                 continue
96             is_ttip = utils.is_tooltip(msgid)
97             # New messages does not yet exists in dest.
98             if msgkey not in dst_messages:
99                 dst_messages[msgkey] = messages[msgkey]
100                 if msgkey in states["fuzzy_msg"]:
101                     done_fuzzy_msgkeys.add(msgkey)
102                     dst_states["fuzzy_msg"].add(msgkey)
103                 elif msgkey in states["trans_msg"]:
104                     done_msgkeys.add(msgkey)
105                     dst_states["trans_msg"].add(msgkey)
106                     dst_stats["trans_msg"] += 1
107                     if is_ttip:
108                         dst_stats["trans_ttips"] += 1
109                 nbr_added += 1
110                 dst_stats["tot_msg"] += 1
111                 if is_ttip:
112                     dst_stats["tot_ttips"] += 1
113             # From now on, the new messages is already in dst.
114             # New message is neither translated nor fuzzy, skip it.
115             elif msgkey not in (states["trans_msg"] | states["fuzzy_msg"]):
116                 continue
117             # From now on, the new message is either translated or fuzzy!
118             # The new message is translated.
119             elif msgkey in states["trans_msg"]:
120                 dst_messages[msgkey]["msgstr_lines"] = messages[msgkey]["msgstr_lines"]
121                 done_msgkeys.add(msgkey)
122                 done_fuzzy_msgkeys.discard(msgkey)
123                 if msgkey in dst_states["fuzzy_msg"]:
124                     dst_states["fuzzy_msg"].remove(msgkey)
125                     nbr_unfuzzied += 1
126                 if msgkey not in dst_states["trans_msg"]:
127                     dst_states["trans_msg"].add(msgkey)
128                     dst_stats["trans_msg"] += 1
129                     if is_ttip:
130                         dst_stats["trans_ttips"] += 1
131                 else:
132                     nbr_replaced += 1
133                 nbr_merged += 1
134             # The new message is fuzzy, org one is fuzzy too,
135             # and this msgkey has not yet been merged.
136             elif msgkey not in (dst_states["trans_msg"] | done_fuzzy_msgkeys):
137                 dst_messages[msgkey]["msgstr_lines"] = messages[msgkey]["msgstr_lines"]
138                 done_fuzzy_msgkeys.add(msgkey)
139                 dst_states["fuzzy_msg"].add(msgkey)
140                 nbr_merged += 1
141                 nbr_replaced += 1
142
143     utils.write_messages(args.dst, dst_messages, dst_states["comm_msg"], dst_states["fuzzy_msg"])
144
145     print("Merged completed. {} messages were merged (among which {} were replaced), " \
146           "{} were added, {} were \"un-fuzzied\"." \
147           "".format(nbr_merged, nbr_replaced, nbr_added, nbr_unfuzzied))
148     if args.stats:
149         print("Final merged po stats:")
150         utils.print_stats(dst_stats, prefix="\t")
151     return ret
152
153
154 if __name__ == "__main__":
155     print("\n\n *** Running {} *** \n".format(__file__))
156     sys.exit(main())