Attachment 'mki18n.py'
Download 1 #! /usr/bin/env python
2 # -*- coding: iso-8859-1 -*-
3 #
4 # PYTHON MODULE: MKI18N.PY
5 # =========
6 #
7 # Abstract: Make Internationalization (i18n) files for an application.
8 #
9 # Written by Pierre Rouleau. 2003. Released to public domain.
10 #
11 # Last update: Wednesday, October 22, 2003. @ 21:08:48.
12 #
13 # File: ROUP2003N01::C:/dev/python/mki18n.py
14 #
15 # RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.2 2003/06/10 10:54:27 PRouleau Exp $
16 #
17 # Update history:
18 #
19 # - File created: Saturday, June 7, 2003. by Pierre Rouleau
20 # - 10/06/03 rcs : RCS Revision 1.1 2003/06/10 10:06:12 PRouleau
21 # - 10/06/03 rcs : RCS Initial revision
22 # - 23/08/03 rcs : RCS Revision 1.2 2003/06/10 10:54:27 PRouleau
23 # - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format. Added the encoding
24 # notification to Python to comply with Python's 2.3 PEP 263.
25 # - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file.
26 # - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient.
27 #
28 # RCS $Log: $
29 #
30 #
31 # -----------------------------------------------------------------------------
32 """
33 mki18n allows you to internationalize your software. You can use it to
34 create the GNU .po files (Portable Object) and the compiled .mo files
35 (Machine Object).
36
37 mki18n module can be used from the command line or from within a script (see
38 the Usage at the end of this page).
39
40 Table of Contents
41 -----------------
42
43 makePO() -- Build the Portable Object file for the application --
44 catPO() -- Concatenate one or several PO files with the application domain files. --
45 makeMO() -- Compile the Portable Object files into the Machine Object stored in the right location. --
46 printUsage -- Displays how to use this script from the command line --
47
48 Scriptexecution -- Runs when invoked from the command line --
49
50
51 NOTE: this module uses GNU gettext utilities.
52
53 You can get the gettext tools from the following sites:
54
55 - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available.
56 Note that you need to use `GNU libiconv`_ to use this. Get it from the `GNU
57 libiconv ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP
58 files and install the packages inside c:/gnu. All binaries will be stored
59 inside c:/gnu/bin. Just put c:/gnu/bin inside your PATH. You will need
60 the following files:
61
62 - `gettext-runtime-0.12.1.bin.woe32.zip`_
63 - `gettext-tools-0.12.1.bin.woe32.zip`_
64 - `libiconv-1.9.1.bin.woe32.zip`_
65
66
67 .. _GNU libiconv: http://www.gnu.org/software/libiconv/
68 .. _GNU libiconv ftp site: http://www.ibiblio.org/pub/gnu/libiconv/
69 .. _gettext-runtime-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip
70 .. _gettext-tools-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip
71 .. _libiconv-1.9.1.bin.woe32.zip: http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip
72
73 """
74 # -----------------------------------------------------------------------------
75 # Module Import
76 # -------------
77 #
78 import os
79 import sys
80 # -----------------------------------------------------------------------------
81 # Global variables
82 # ----------------
83 #
84
85 __author__ = "Pierre Rouleau"
86 __version__= "$Revision: 1.2 $"
87
88 # -----------------------------------------------------------------------------
89 # Public variables
90 # ----------------
91 #
92 # iso639 : natural
93 # 2-letter : language
94 # code : name
95 iso639_languageDict = { 'aa' : 'Afar. ',
96 'ab' : 'Abkhazian. ',
97 'ae' : 'Avestan. ',
98 'af' : 'Afrikaans. ',
99 'am' : 'Amharic. ',
100 'ar' : 'Arabic. ',
101 'as' : 'Assamese. ',
102 'ay' : 'Aymara. ',
103 'az' : 'Azerbaijani. ',
104 'ba' : 'Bashkir. ',
105 'be' : 'Byelorussian; Belarusian. ',
106 'bg' : 'Bulgarian. ',
107 'bh' : 'Bihari. ',
108 'bi' : 'Bislama. ',
109 'bn' : 'Bengali; Bangla. ',
110 'bo' : 'Tibetan. ',
111 'br' : 'Breton. ',
112 'bs' : 'Bosnian. ',
113 'ca' : 'Catalan. ',
114 'ce' : 'Chechen. ',
115 'ch' : 'Chamorro. ',
116 'co' : 'Corsican. ',
117 'cs' : 'Czech. ',
118 'cu' : 'Church Slavic. ',
119 'cv' : 'Chuvash. ',
120 'cy' : 'Welsh. ',
121 'da' : 'Danish. ',
122 'de' : 'German. ',
123 'dz' : 'Dzongkha; Bhutani. ',
124 'el' : 'Greek. ',
125 'en' : 'English. ',
126 'eo' : 'Esperanto. ',
127 'es' : 'Spanish. ',
128 'et' : 'Estonian. ',
129 'eu' : 'Basque. ',
130 'fa' : 'Persian. ',
131 'fi' : 'Finnish. ',
132 'fj' : 'Fijian; Fiji. ',
133 'fo' : 'Faroese. ',
134 'fr' : 'French. ',
135 'fy' : 'Frisian. ',
136 'ga' : 'Irish. ',
137 'gd' : 'Scots; Gaelic. ',
138 'gl' : 'Gallegan; Galician. ',
139 'gn' : 'Guarani. ',
140 'gu' : 'Gujarati. ',
141 'gv' : 'Manx. ',
142 'ha' : 'Hausa (?). ',
143 'he' : 'Hebrew (formerly iw). ',
144 'hi' : 'Hindi. ',
145 'ho' : 'Hiri Motu. ',
146 'hr' : 'Croatian. ',
147 'hu' : 'Hungarian. ',
148 'hy' : 'Armenian. ',
149 'hz' : 'Herero. ',
150 'ia' : 'Interlingua. ',
151 'id' : 'Indonesian (formerly in). ',
152 'ie' : 'Interlingue. ',
153 'ik' : 'Inupiak. ',
154 'io' : 'Ido. ',
155 'is' : 'Icelandic. ',
156 'it' : 'Italian. ',
157 'iu' : 'Inuktitut. ',
158 'ja' : 'Japanese. ',
159 'jv' : 'Javanese. ',
160 'ka' : 'Georgian. ',
161 'ki' : 'Kikuyu. ',
162 'kj' : 'Kuanyama. ',
163 'kk' : 'Kazakh. ',
164 'kl' : 'Kalaallisut; Greenlandic. ',
165 'km' : 'Khmer; Cambodian. ',
166 'kn' : 'Kannada. ',
167 'ko' : 'Korean. ',
168 'ks' : 'Kashmiri. ',
169 'ku' : 'Kurdish. ',
170 'kv' : 'Komi. ',
171 'kw' : 'Cornish. ',
172 'ky' : 'Kirghiz. ',
173 'la' : 'Latin. ',
174 'lb' : 'Letzeburgesch. ',
175 'ln' : 'Lingala. ',
176 'lo' : 'Lao; Laotian. ',
177 'lt' : 'Lithuanian. ',
178 'lv' : 'Latvian; Lettish. ',
179 'mg' : 'Malagasy. ',
180 'mh' : 'Marshall. ',
181 'mi' : 'Maori. ',
182 'mk' : 'Macedonian. ',
183 'ml' : 'Malayalam. ',
184 'mn' : 'Mongolian. ',
185 'mo' : 'Moldavian. ',
186 'mr' : 'Marathi. ',
187 'ms' : 'Malay. ',
188 'mt' : 'Maltese. ',
189 'my' : 'Burmese. ',
190 'na' : 'Nauru. ',
191 'nb' : 'Norwegian Bokmål. ',
192 'nd' : 'Ndebele, North. ',
193 'ne' : 'Nepali. ',
194 'ng' : 'Ndonga. ',
195 'nl' : 'Dutch. ',
196 'nn' : 'Norwegian Nynorsk. ',
197 'no' : 'Norwegian. ',
198 'nr' : 'Ndebele, South. ',
199 'nv' : 'Navajo. ',
200 'ny' : 'Chichewa; Nyanja. ',
201 'oc' : 'Occitan; Provençal. ',
202 'om' : '(Afan) Oromo. ',
203 'or' : 'Oriya. ',
204 'os' : 'Ossetian; Ossetic. ',
205 'pa' : 'Panjabi; Punjabi. ',
206 'pi' : 'Pali. ',
207 'pl' : 'Polish. ',
208 'ps' : 'Pashto, Pushto. ',
209 'pt' : 'Portuguese. ',
210 'qu' : 'Quechua. ',
211 'rm' : 'Rhaeto-Romance. ',
212 'rn' : 'Rundi; Kirundi. ',
213 'ro' : 'Romanian. ',
214 'ru' : 'Russian. ',
215 'rw' : 'Kinyarwanda. ',
216 'sa' : 'Sanskrit. ',
217 'sc' : 'Sardinian. ',
218 'sd' : 'Sindhi. ',
219 'se' : 'Northern Sami. ',
220 'sg' : 'Sango; Sangro. ',
221 'si' : 'Sinhalese. ',
222 'sk' : 'Slovak. ',
223 'sl' : 'Slovenian. ',
224 'sm' : 'Samoan. ',
225 'sn' : 'Shona. ',
226 'so' : 'Somali. ',
227 'sq' : 'Albanian. ',
228 'sr' : 'Serbian. ',
229 'ss' : 'Swati; Siswati. ',
230 'st' : 'Sesotho; Sotho, Southern. ',
231 'su' : 'Sundanese. ',
232 'sv' : 'Swedish. ',
233 'sw' : 'Swahili. ',
234 'ta' : 'Tamil. ',
235 'te' : 'Telugu. ',
236 'tg' : 'Tajik. ',
237 'th' : 'Thai. ',
238 'ti' : 'Tigrinya. ',
239 'tk' : 'Turkmen. ',
240 'tl' : 'Tagalog. ',
241 'tn' : 'Tswana; Setswana. ',
242 'to' : 'Tonga (?). ',
243 'tr' : 'Turkish. ',
244 'ts' : 'Tsonga. ',
245 'tt' : 'Tatar. ',
246 'tw' : 'Twi. ',
247 'ty' : 'Tahitian. ',
248 'ug' : 'Uighur. ',
249 'uk' : 'Ukrainian. ',
250 'ur' : 'Urdu. ',
251 'uz' : 'Uzbek. ',
252 'vi' : 'Vietnamese. ',
253 'vo' : 'Volapük; Volapuk. ',
254 'wa' : 'Walloon. ',
255 'wo' : 'Wolof. ',
256 'xh' : 'Xhosa. ',
257 'yi' : 'Yiddish (formerly ji). ',
258 'yo' : 'Yoruba. ',
259 'za' : 'Zhuang. ',
260 'zh' : 'Chinese. ',
261 'zu' : 'Zulu.'
262 }
263
264
265 # -----------------------------------------------------------------------------
266 # m a k e P O ( ) -- Build the Portable Object file for the application --
267 # ^^^^^^^^^^^^^^^
268 #
269 def makePO(applicationDirectoryPath, applicationDomain=None, verbose=0) :
270 """Build the Portable Object Template file for the application.
271
272 makePO builds the .pot file for the application stored inside
273 a specified directory by running xgettext for all application source
274 files. It finds the name of all files by looking for a file called 'app.fil'.
275 If this file does not exists, makePo raises an IOError exception.
276 By default the application domain (the application
277 name) is the same as the directory name but it can be overridden by the
278 'applicationDomain' argument.
279
280 makePO always creates a new file called messages.pot. If it finds files
281 of the form app_xx.po where 'app' is the application name and 'xx' is one
282 of the ISO 639 two-letter language codes, makePO resynchronizes those
283 files with the latest extracted strings (now contained in messages.pot).
284 This process updates all line location number in the language-specific
285 .po files and may also create new entries for translation (or comment out
286 some). The .po file is not changed, instead a new file is created with
287 the .new extension appended to the name of the .po file.
288
289 By default the function does not display what it is doing. Set the
290 verbose argument to 1 to force it to print its commands.
291 """
292
293 if applicationDomain is None:
294 applicationName = fileBaseOf(applicationDirectoryPath,withPath=0)
295 else:
296 applicationName = applicationDomain
297 currentDir = os.getcwd()
298 os.chdir(applicationDirectoryPath)
299 if not os.path.exists('app.fil'):
300 raise IOError(2,'No module file: app.fil')
301
302 # Steps:
303 # Use xgettext to parse all application modules
304 # The following switches are used:
305 #
306 # -s : sort output by string content (easier to use when we need to merge several .po files)
307 # --files-from=app.fil : The list of files is taken from the file: app.fil
308 # --output= : specifies the name of the output file (using a .pot extension)
309 cmd = 'xgettext -s --no-wrap --files-from=app.fil --output=messages.pot'
310 if verbose: print cmd
311 os.system(cmd)
312
313 for langCode in iso639_languageDict.keys():
314 if langCode == 'en':
315 pass
316 else:
317 langPOfileName = "%s_%s.po" % (applicationName , langCode)
318 if os.path.exists(langPOfileName):
319 cmd = "msgmerge -s --no-wrap %s messages.pot > %s.new" % (langPOfileName, langPOfileName)
320 if verbose: print cmd
321 os.system(cmd)
322 os.chdir(currentDir)
323
324 # -----------------------------------------------------------------------------
325 # c a t P O ( ) -- Concatenate one or several PO files with the application domain files. --
326 # ^^^^^^^^^^^^^
327 #
328 def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0) :
329 """Concatenate one or several PO files with the application domain files.
330 """
331
332 if applicationDomain is None:
333 applicationName = fileBaseOf(applicationDirectoryPath,withPath=0)
334 else:
335 applicationName = applicationDomain
336 currentDir = os.getcwd()
337 os.chdir(applicationDirectoryPath)
338
339 for langCode in iso639_languageDict.keys():
340 if langCode == 'en':
341 pass
342 else:
343 langPOfileName = "%s_%s.po" % (applicationName , langCode)
344 if os.path.exists(langPOfileName):
345 fileList = ''
346 for fileName in listOf_extraPo:
347 fileList += ("%s_%s.po " % (fileName,langCode))
348 cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName)
349 if verbose: print cmd
350 os.system(cmd)
351 if targetDir is None:
352 pass
353 else:
354 mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode)
355 cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir,applicationName,applicationName,langCode)
356 if verbose: print cmd
357 os.system(cmd)
358 os.chdir(currentDir)
359
360 # -----------------------------------------------------------------------------
361 # m a k e M O ( ) -- Compile the Portable Object files into the Machine Object stored in the right location. --
362 # ^^^^^^^^^^^^^^^
363 #
364 def makeMO(applicationDirectoryPath,targetDir='./locale',applicationDomain=None, verbose=0, forceEnglish=0) :
365 """Compile the Portable Object files into the Machine Object stored in the right location.
366
367 makeMO converts all translated language-specific PO files located inside
368 the application directory into the binary .MO files stored inside the
369 LC_MESSAGES sub-directory for the found locale files.
370
371 makeMO searches for all files that have a name of the form 'app_xx.po'
372 inside the application directory specified by the first argument. The
373 'app' is the application domain name (that can be specified by the
374 applicationDomain argument or is taken from the directory name). The 'xx'
375 corresponds to one of the ISO 639 two-letter language codes.
376
377 makeMo stores the resulting files inside a sub-directory of `targetDir`
378 called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language
379 code.
380 """
381 if targetDir is None:
382 targetDir = './locale'
383 if verbose:
384 print "Target directory for .mo files is: %s" % targetDir
385
386 if applicationDomain is None:
387 applicationName = fileBaseOf(applicationDirectoryPath,withPath=0)
388 else:
389 applicationName = applicationDomain
390 currentDir = os.getcwd()
391 os.chdir(applicationDirectoryPath)
392
393 for langCode in iso639_languageDict.keys():
394 if (langCode == 'en') and (forceEnglish==0):
395 pass
396 else:
397 langPOfileName = "%s_%s.po" % (applicationName , langCode)
398 if os.path.exists(langPOfileName):
399 mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode)
400 if not os.path.exists(mo_targetDir):
401 mkdir(mo_targetDir)
402 cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po" % (mo_targetDir,applicationName,applicationName,langCode)
403 if verbose: print cmd
404 os.system(cmd)
405 os.chdir(currentDir)
406
407 # -----------------------------------------------------------------------------
408 # p r i n t U s a g e -- Displays how to use this script from the command line --
409 # ^^^^^^^^^^^^^^^^^^^
410 #
411 def printUsage(errorMsg=None) :
412 """Displays how to use this script from the command line."""
413 print """
414 ##################################################################################
415 # mki18n : Make internationalization files. #
416 # Uses the GNU gettext system to create PO (Portable Object) files #
417 # from source code, coimpile PO into MO (Machine Object) files. #
418 # Supports C,C++,Python source files. #
419 # #
420 # Usage: mki18n {OPTION} [appDirPath] #
421 # #
422 # Options: #
423 # -e : When -m is used, forces English .mo file creation #
424 # -h : prints this help #
425 # -m : make MO from existing PO files #
426 # -p : make PO, update PO files: Creates a new messages.pot #
427 # file. Creates a dom_xx.po.new for every existing #
428 # language specific .po file. ('xx' stands for the ISO639 #
429 # two-letter language code and 'dom' stands for the #
430 # application domain name). mki18n requires that you #
431 # write a 'app.fil' file which contains the list of all #
432 # source code to parse. #
433 # -v : verbose (prints comments while running) #
434 # --domain=appName : specifies the application domain name. By default #
435 # the directory name is used. #
436 # --moTarget=dir : specifies the directory where .mo files are stored. #
437 # If not specified, the target is './locale' #
438 # #
439 # You must specify one of the -p or -m option to perform the work. You can #
440 # specify the path of the target application. If you leave it out mki18n #
441 # will use the current directory as the application main directory. #
442 # #
443 ##################################################################################"""
444 if errorMsg:
445 print "\n ERROR: %s" % errorMsg
446
447 # -----------------------------------------------------------------------------
448 # f i l e B a s e O f ( ) -- Return base name of filename --
449 # ^^^^^^^^^^^^^^^^^^^^^^^
450 #
451 def fileBaseOf(filename,withPath=0) :
452 """fileBaseOf(filename,withPath) ---> string
453
454 Return base name of filename. The returned string never includes the extension.
455 Use os.path.basename() to return the basename with the extension. The
456 second argument is optional. If specified and if set to 'true' (non zero)
457 the string returned contains the full path of the file name. Otherwise the
458 path is excluded.
459
460 [Example]
461 >>> fn = 'd:/dev/telepath/tvapp/code/test.html'
462 >>> fileBaseOf(fn)
463 'test'
464 >>> fileBaseOf(fn)
465 'test'
466 >>> fileBaseOf(fn,1)
467 'd:/dev/telepath/tvapp/code/test'
468 >>> fileBaseOf(fn,0)
469 'test'
470 >>> fn = 'abcdef'
471 >>> fileBaseOf(fn)
472 'abcdef'
473 >>> fileBaseOf(fn,1)
474 'abcdef'
475 >>> fn = "abcdef."
476 >>> fileBaseOf(fn)
477 'abcdef'
478 >>> fileBaseOf(fn,1)
479 'abcdef'
480 """
481 pos = filename.rfind('.')
482 if pos > 0:
483 filename = filename[:pos]
484 if withPath:
485 return filename
486 else:
487 return os.path.basename(filename)
488 # -----------------------------------------------------------------------------
489 # m k d i r ( ) -- Create a directory (and possibly the entire tree) --
490 # ^^^^^^^^^^^^^
491 #
492 def mkdir(directory) :
493 """Create a directory (and possibly the entire tree).
494
495 The os.mkdir() will fail to create a directory if one of the
496 directory in the specified path does not exist. mkdir()
497 solves this problem. It creates every intermediate directory
498 required to create the final path. Under Unix, the function
499 only supports forward slash separator, but under Windows and MacOS
500 the function supports the forward slash and the OS separator (backslash
501 under windows).
502 """
503
504 # translate the path separators
505 directory = unixpath(directory)
506 # build a list of all directory elements
507 aList = filter(lambda x: len(x)>0, directory.split('/'))
508 theLen = len(aList)
509 # if the first element is a Windows-style disk drive
510 # concatenate it with the first directory
511 if aList[0].endswith(':'):
512 if theLen > 1:
513 aList[1] = aList[0] + '/' + aList[1]
514 del aList[0]
515 theLen -= 1
516 # if the original directory starts at root,
517 # make sure the first element of the list
518 # starts at root too
519 if directory[0] == '/':
520 aList[0] = '/' + aList[0]
521 # Now iterate through the list, check if the
522 # directory exists and if not create it
523 theDir = ''
524 for i in range(theLen):
525 theDir += aList[i]
526 if not os.path.exists(theDir):
527 os.mkdir(theDir)
528 theDir += '/'
529 # -----------------------------------------------------------------------------
530 # S c r i p t e x e c u t i o n -- Runs when invoked from the command line --
531 # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
532 #
533 if __name__ == "__main__":
534 import getopt # command line parsing
535 argc = len(sys.argv)
536 if argc == 1:
537 printUsage('Missing argument: specify at least one of -m or -p (or both).')
538 sys.exit(1)
539 # If there is some arguments, parse the command line
540 validOptions = "ehmpv"
541 validLongOptions = ['domain=', 'moTarget=']
542 option = {}
543 option['forceEnglish'] = 0
544 option['mo'] = 0
545 option['po'] = 0
546 option['verbose'] = 0
547 option['domain'] = None
548 option['moTarget'] = None
549 try:
550 optionList,pargs = getopt.getopt(sys.argv[1:],validOptions,validLongOptions)
551 except getopt.GetoptError, e:
552 printUsage(e[0])
553 sys.exit(1)
554 for (opt,val) in optionList:
555 if (opt == '-h'):
556 printUsage()
557 sys.exit(0)
558 elif (opt == '-e'): option['forceEnglish'] = 1
559 elif (opt == '-m'): option['mo'] = 1
560 elif (opt == '-p'): option['po'] = 1
561 elif (opt == '-v'): option['verbose'] = 1
562 elif (opt == '--domain'): option['domain'] = val
563 elif (opt == '--moTarget'): option['moTarget'] = val
564 if len(pargs) == 0:
565 appDirPath = os.getcwd()
566 if option['verbose']:
567 print "No project directory given. Using current one: %s" % appDirPath
568 elif len(pargs) == 1:
569 appDirPath = pargs[0]
570 else:
571 printUsage('Too many arguments (%u). Use double quotes if you have space in directory name' % len(pargs))
572 sys.exit(1)
573 if option['domain'] is None:
574 # If no domain specified, use the name of the target directory
575 option['domain'] = fileBaseOf(appDirPath)
576 if option['verbose']:
577 print "Application domain used is: '%s'" % option['domain']
578 if option['po']:
579 try:
580 makePO(appDirPath,option['domain'],option['verbose'])
581 except IOError, e:
582 printUsage(e[1] + '\n You must write a file app.fil that contains the list of all files to parse.')
583 if option['mo']:
584 makeMO(appDirPath,option['moTarget'],option['domain'],option['verbose'],option['forceEnglish'])
585 sys.exit(1)
586
587
588 # -----------------------------------------------------------------------------
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.