2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Verifies that GRD resource files define all the strings used by a given
7 set of source files. For file formats where it is not possible to infer which
8 strings represent message identifiers, localized strings should be explicitly
9 annotated with the string "i18n-content", for example:
11 LocalizeString(/*i18n-content*/"PRODUCT_NAME");
13 This script also recognises localized strings in HTML and manifest.json files:
15 HTML: i18n-content="PRODUCT_NAME"
16 or i18n-value-name-1="BUTTON_NAME"
17 or i18n-title="TOOLTIP_NAME"
18 manifest.json: __MSG_PRODUCT_NAME__
20 Note that these forms must be exact; extra spaces are not permitted, though
21 either single or double quotes are recognized.
23 In addition, the script checks that all the messages are still in use; if
24 this is not the case then a warning is issued, but the script still succeeds.
32 import xml
.dom
.minidom
as minidom
35 To remove this warning, either remove the unused tags from
36 resource files, add the files that use the tags listed above to
37 remoting.gyp, or annotate existing uses of those tags with the
38 prefix /*i18n-content*/
41 def LoadTagsFromGrd(filename
):
42 xml
= minidom
.parse(filename
)
45 msgs_and_structs
= xml
.getElementsByTagName("message")
46 msgs_and_structs
.extend(xml
.getElementsByTagName("structure"))
47 for res
in msgs_and_structs
:
48 name
= res
.getAttribute("name")
49 if not name
or not name
.startswith("IDS_"):
50 raise Exception("Tag name doesn't start with IDS_: %s" % name
)
52 if 'android_java' in res
.getAttribute('formatter_data'):
53 android_tags
.append(name
)
55 other_tags
.append(name
)
56 return android_tags
, other_tags
59 def ExtractTagFromLine(file_type
, line
):
60 """Extract a tag from a line of HTML, C++, JS or JSON."""
61 if file_type
== "html":
63 m
= re
.search('i18n-content=[\'"]([^\'"]*)[\'"]', line
)
64 if m
: return m
.group(1)
66 m
= re
.search('i18n-title=[\'"]([^\'"]*)[\'"]', line
)
67 if m
: return m
.group(1)
68 # HTML-style (substitutions)
69 m
= re
.search('i18n-value-name-[1-9]=[\'"]([^\'"]*)[\'"]', line
)
70 if m
: return m
.group(1)
71 elif file_type
== 'js':
73 m
= re
.search('/\*i18n-content\*/[\'"]([^\`"]*)[\'"]', line
)
74 if m
: return m
.group(1)
75 elif file_type
== 'cc' or file_type
== 'mm':
77 m
= re
.search('IDS_([A-Z0-9_]*)', line
)
78 if m
: return m
.group(1)
79 m
= re
.search('/\*i18n-content\*/["]([^\`"]*)["]', line
)
80 if m
: return m
.group(1)
81 elif file_type
== 'json.jinja2':
83 m
= re
.search('__MSG_(.*)__', line
)
84 if m
: return m
.group(1)
85 elif file_type
== 'jinja2':
86 # Jinja2 template file
87 m
= re
.search('\{\%\s+trans\s+\%\}([A-Z0-9_]+)\{\%\s+endtrans\s+\%\}', line
)
88 if m
: return m
.group(1)
92 def VerifyFile(filename
, messages
, used_tags
):
94 Parse |filename|, looking for tags and report any that are not included in
95 |messages|. Return True if all tags are present and correct, or False if
99 base_name
, file_type
= os
.path
.splitext(filename
)
100 file_type
= file_type
[1:]
101 if file_type
== 'jinja2' and base_name
.endswith('.json'):
102 file_type
= 'json.jinja2'
103 if file_type
not in ['js', 'cc', 'html', 'json.jinja2', 'jinja2', 'mm']:
104 raise Exception("Unknown file type: %s" % file_type
)
108 f
= open(filename
, 'r')
109 lines
= f
.readlines()
110 for i
in xrange(0, len(lines
)):
111 tag
= ExtractTagFromLine(file_type
, lines
[i
])
116 if not tag
in messages
:
118 print '%s/%s:%d: error: Undefined tag: %s' % \
119 (os
.getcwd(), filename
, i
+ 1, tag
)
125 parser
= optparse
.OptionParser(
126 usage
='Usage: %prog [options...] [source_file...]')
127 parser
.add_option('-t', '--touch', dest
='touch',
128 help='File to touch when finished.')
129 parser
.add_option('-r', '--grd', dest
='grd', action
='append',
131 parser
.add_option('--strict', dest
='strict', action
='store_true',
132 help='Use strict verification checks.')
134 options
, args
= parser
.parse_args()
135 if not options
.touch
:
136 print '-t is not specified.'
138 if len(options
.grd
) == 0 or len(args
) == 0:
139 print 'At least one GRD file needs to be specified.'
143 non_android_resources
= []
144 for f
in options
.grd
:
145 android_tags
, other_tags
= LoadTagsFromGrd(f
)
146 all_resources
.extend(android_tags
+ other_tags
)
147 non_android_resources
.extend(other_tags
)
152 if not VerifyFile(f
, all_resources
, used_tags
):
157 # Determining if a resource is being used in the Android app is tricky
158 # because it requires annotating and parsing Android XML layout files.
159 # For now, exclude Android strings from this check.
160 for tag
in non_android_resources
:
161 if tag
not in used_tags
:
162 print ('%s/%s:0: warning: %s is defined but not used') % \
163 (os
.getcwd(), sys
.argv
[2], tag
)
166 print WARNING_MESSAGE
169 f
= open(options
.touch
, 'a')
171 os
.utime(options
.touch
, None)
176 if __name__
== '__main__':