From 48b354abf4a795ee6f3ea9bddb5d52693914e244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Fri, 4 Nov 2022 20:34:59 +0100 Subject: [PATCH] Update translations --- iwla.pot | 425 ----------- locales/fr_FR/LC_MESSAGES/iwla.mo | Bin 4314 -> 4488 bytes .../fr_FR/LC_MESSAGES/{iwla.pot => iwla.po} | 265 +++---- tools/gettext.sh | 4 +- tools/pygettext.py | 684 ++++++++++++++++++ 5 files changed, 799 insertions(+), 579 deletions(-) delete mode 100644 iwla.pot rename locales/fr_FR/LC_MESSAGES/{iwla.pot => iwla.po} (53%) create mode 100755 tools/pygettext.py diff --git a/iwla.pot b/iwla.pot deleted file mode 100644 index 22328b1..0000000 --- a/iwla.pot +++ /dev/null @@ -1,425 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2016-04-12 08:34+CEST\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: ENCODING\n" -"Generated-By: pygettext.py 1.5\n" - - -#: display.py:32 -msgid "April" -msgstr "" - -#: display.py:32 -msgid "February" -msgstr "" - -#: display.py:32 -msgid "January" -msgstr "" - -#: display.py:32 -msgid "July" -msgstr "" - -#: display.py:32 -msgid "March" -msgstr "" - -#: display.py:32 iwla.py:472 -msgid "June" -msgstr "" - -#: display.py:32 iwla.py:472 -msgid "May" -msgstr "" - -#: display.py:33 -msgid "August" -msgstr "" - -#: display.py:33 -msgid "December" -msgstr "" - -#: display.py:33 -msgid "November" -msgstr "" - -#: display.py:33 -msgid "October" -msgstr "" - -#: display.py:33 -msgid "September" -msgstr "" - -#: display.py:195 -msgid "Ratio" -msgstr "" - -#: iwla.py:413 -msgid "Statistics" -msgstr "" - -#: iwla.py:421 iwla.py:474 -msgid "Not viewed Bandwidth" -msgstr "" - -#: iwla.py:421 iwla.py:474 -msgid "Visits" -msgstr "" - -#: iwla.py:421 iwla.py:474 plugins/display/all_visits.py:70 -#: plugins/display/feeds.py:76 plugins/display/hours_stats.py:73 -#: plugins/display/hours_stats.py:83 plugins/display/referers.py:95 -#: plugins/display/referers.py:153 plugins/display/top_downloads.py:97 -#: plugins/display/top_visitors.py:72 plugins/display/track_users.py:113 -msgid "Hits" -msgstr "" - -#: iwla.py:421 iwla.py:474 plugins/display/all_visits.py:70 -#: plugins/display/feeds.py:76 plugins/display/hours_stats.py:73 -#: plugins/display/hours_stats.py:83 plugins/display/referers.py:95 -#: plugins/display/referers.py:153 plugins/display/top_visitors.py:72 -#: plugins/display/track_users.py:77 plugins/display/track_users.py:113 -msgid "Pages" -msgstr "" - -#: iwla.py:421 iwla.py:474 plugins/display/all_visits.py:70 -#: plugins/display/hours_stats.py:73 plugins/display/hours_stats.py:83 -#: plugins/display/top_visitors.py:72 -msgid "Bandwidth" -msgstr "" - -#: iwla.py:421 plugins/display/hours_stats.py:71 -msgid "By day" -msgstr "" - -#: iwla.py:421 plugins/display/hours_stats.py:73 -msgid "Day" -msgstr "" - -#: iwla.py:458 -msgid "Average" -msgstr "" - -#: iwla.py:463 iwla.py:508 -msgid "Total" -msgstr "" - -#: iwla.py:472 -msgid "Apr" -msgstr "" - -#: iwla.py:472 -msgid "Aug" -msgstr "" - -#: iwla.py:472 -msgid "Dec" -msgstr "" - -#: iwla.py:472 -msgid "Feb" -msgstr "" - -#: iwla.py:472 -msgid "Jan" -msgstr "" - -#: iwla.py:472 -msgid "Jul" -msgstr "" - -#: iwla.py:472 -msgid "Mar" -msgstr "" - -#: iwla.py:472 -msgid "Nov" -msgstr "" - -#: iwla.py:472 -msgid "Oct" -msgstr "" - -#: iwla.py:472 -msgid "Sep" -msgstr "" - -#: iwla.py:473 -msgid "Summary" -msgstr "" - -#: iwla.py:474 -msgid "Month" -msgstr "" - -#: iwla.py:474 iwla.py:486 plugins/display/feeds.py:99 -#: plugins/display/ip_to_geo.py:109 plugins/display/operating_systems.py:90 -#: plugins/display/track_users.py:108 -msgid "Details" -msgstr "" - -#: iwla.py:474 plugins/display/ip_to_geo.py:96 -#: plugins/display/ip_to_geo.py:114 -msgid "Visitors" -msgstr "" - -#: iwla.py:522 -msgid "Statistics for" -msgstr "" - -#: iwla.py:529 -msgid "Last update" -msgstr "" - -#: iwla.py:533 -msgid "Time analysis" -msgstr "" - -#: iwla.py:535 -msgid "hours" -msgstr "" - -#: iwla.py:536 -msgid "minutes" -msgstr "" - -#: iwla.py:536 -msgid "seconds" -msgstr "" - -#: plugins/display/all_visits.py:70 plugins/display/feeds.py:76 -#: plugins/display/ip_to_geo.py:64 plugins/display/top_visitors.py:72 -#: plugins/display/track_users.py:113 -msgid "Host" -msgstr "" - -#: plugins/display/all_visits.py:70 plugins/display/top_visitors.py:72 -msgid "Last seen" -msgstr "" - -#: plugins/display/all_visits.py:92 -msgid "All visits" -msgstr "" - -#: plugins/display/all_visits.py:93 plugins/display/top_visitors.py:72 -msgid "Top visitors" -msgstr "" - -#: plugins/display/browsers.py:79 -msgid "Browsers" -msgstr "" - -#: plugins/display/browsers.py:79 plugins/display/browsers.py:113 -msgid "Browser" -msgstr "" - -#: plugins/display/browsers.py:79 plugins/display/browsers.py:113 -#: plugins/display/operating_systems.py:78 -#: plugins/display/operating_systems.py:95 plugins/display/top_hits.py:71 -#: plugins/display/top_hits.py:97 plugins/display/top_pages.py:71 -#: plugins/display/top_pages.py:96 -msgid "Entrance" -msgstr "" - -#: plugins/display/browsers.py:98 plugins/display/browsers.py:128 -#: plugins/display/referers.py:110 plugins/display/referers.py:125 -#: plugins/display/referers.py:140 plugins/display/referers.py:163 -#: plugins/display/referers.py:174 plugins/display/referers.py:185 -#: plugins/display/referers.py:222 plugins/display/top_downloads.py:83 -#: plugins/display/top_downloads.py:103 plugins/display/top_hits.py:82 -#: plugins/display/top_hits.py:103 plugins/display/top_pages.py:82 -#: plugins/display/top_pages.py:102 plugins/display/top_visitors.py:92 -msgid "Others" -msgstr "" - -#: plugins/display/browsers.py:105 -msgid "Top Browsers" -msgstr "" - -#: plugins/display/browsers.py:107 -msgid "All Browsers" -msgstr "" - -#: plugins/display/browsers.py:123 -msgid "Unknown" -msgstr "" - -#: plugins/display/feeds.py:70 -msgid "All Feeds parsers" -msgstr "" - -#: plugins/display/feeds.py:76 -msgid "All feeds parsers" -msgstr "" - -#: plugins/display/feeds.py:92 -msgid "Merged feeds parsers" -msgstr "" - -#: plugins/display/feeds.py:97 -msgid "Feeds parsers" -msgstr "" - -#: plugins/display/feeds.py:104 -msgid "Found" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Fri" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Mon" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Sat" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Sun" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Thu" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Tue" -msgstr "" - -#: plugins/display/hours_stats.py:72 -msgid "Wed" -msgstr "" - -#: plugins/display/hours_stats.py:81 -msgid "By Hours" -msgstr "" - -#: plugins/display/hours_stats.py:83 -msgid "Hours" -msgstr "" - -#: plugins/display/ip_to_geo.py:96 -msgid "Country" -msgstr "" - -#: plugins/display/ip_to_geo.py:96 plugins/display/ip_to_geo.py:107 -#: plugins/display/ip_to_geo.py:114 -msgid "Countries" -msgstr "" - -#: plugins/display/operating_systems.py:78 -#: plugins/display/operating_systems.py:88 -msgid "Operating Systems" -msgstr "" - -#: plugins/display/operating_systems.py:78 -#: plugins/display/operating_systems.py:95 -msgid "Operating System" -msgstr "" - -#: plugins/display/referers.py:95 -msgid "Connexion from" -msgstr "" - -#: plugins/display/referers.py:95 plugins/display/referers.py:153 -msgid "Origin" -msgstr "" - -#: plugins/display/referers.py:99 plugins/display/referers.py:156 -msgid "Search Engine" -msgstr "" - -#: plugins/display/referers.py:114 plugins/display/referers.py:167 -msgid "External URL" -msgstr "" - -#: plugins/display/referers.py:129 plugins/display/referers.py:178 -msgid "External URL (robot)" -msgstr "" - -#: plugins/display/referers.py:147 -msgid "Top Referers" -msgstr "" - -#: plugins/display/referers.py:149 -msgid "All Referers" -msgstr "" - -#: plugins/display/referers.py:193 -msgid "All Key Phrases" -msgstr "" - -#: plugins/display/referers.py:200 plugins/display/referers.py:216 -msgid "Key phrase" -msgstr "" - -#: plugins/display/referers.py:200 plugins/display/referers.py:216 -msgid "Search" -msgstr "" - -#: plugins/display/referers.py:200 plugins/display/referers_diff.py:56 -msgid "Key phrases" -msgstr "" - -#: plugins/display/referers.py:210 -msgid "Top key phrases" -msgstr "" - -#: plugins/display/referers.py:212 -msgid "All key phrases" -msgstr "" - -#: plugins/display/top_downloads.py:71 -msgid "Hit" -msgstr "" - -#: plugins/display/top_downloads.py:71 plugins/display/top_downloads.py:91 -#: plugins/display/top_downloads_diff.py:56 -msgid "All Downloads" -msgstr "" - -#: plugins/display/top_downloads.py:71 plugins/display/top_downloads.py:97 -#: plugins/display/top_hits.py:71 plugins/display/top_hits.py:97 -#: plugins/display/top_pages.py:71 plugins/display/top_pages.py:96 -msgid "URI" -msgstr "" - -#: plugins/display/top_downloads.py:89 -msgid "Top Downloads" -msgstr "" - -#: plugins/display/top_hits.py:71 plugins/display/top_hits.py:91 -msgid "All Hits" -msgstr "" - -#: plugins/display/top_pages.py:71 plugins/display/top_pages.py:90 -msgid "All Pages" -msgstr "" - -#: plugins/display/top_pages.py:88 -msgid "Top Pages" -msgstr "" - -#: plugins/display/track_users.py:77 plugins/display/track_users.py:106 -msgid "Tracked users" -msgstr "" - -#: plugins/display/track_users.py:77 plugins/display/track_users.py:113 -msgid "Last Access" -msgstr "" - diff --git a/locales/fr_FR/LC_MESSAGES/iwla.mo b/locales/fr_FR/LC_MESSAGES/iwla.mo index 8fe7195da6030297e4442dd414b5769663eb9cdb..08c9cf30b9d3c83cbb02ae96ce2fd13fd3dc1a09 100644 GIT binary patch literal 4488 zcmZ{lZH!!18OIM4d|3n(U$7PRfIwTyYjrcelHx+g)~c+k#ZRJ9BpSwlnt* z_hq)@2Qfmz2V#htCN`18@Ifgtz9eXxU_xX=QcR47kQhGH#6%NJ;7b#0{6OOGfA87d zX^SWG+~0Z5>vNv-oM--a$4zGqWgfW;d2pREPs2SQ;X@h!m@%J#6Yy4e41NmMAwSdM zb31$rejGjzHU1pbxS#p{3-B}O7hxZ~1lh{G3vY(+dHpif)>q(1;RjIb-poh(?T{vO zr>BJ)zZu>J2OvMQpO3~*K<)FW=N#1h$9(_1*H1y&okH39U8s3yeg8SA^S=nS&#$5E zehq5e8=h~%o6!H{`4-f;zxw`5P;Hlp|GwvCDF6Hi-U4q#5pRPunGIgQ7i!-D zsP%VveF)0_eO|Bn{^P#=q;F54{Lu0GQ&4t24L=E=gSx+;K>7EVp1+0K@AsasLyf-( zWzRcM<1az^>mR=TGL-#SAiv^J^5ZQWPWx_vntzYy{ZR8CfV!8RULS>8r|R2JLdD-H zuYViLzGtD<`@ZK7p!PWnwf_rH=YI*x-rqotzW|H#L5+Xi^N;ZK4aU3$3;$x=2KuY; zvoM0P_ZdES!XHBU9(Op1VBvL)kM5`I$*R zI>!?blT5?wDb%^X1GWFNQ1SI+&lfyj^!z!LKYsx=?{}WBL9Kfc%Ko>Z)_K?WzYldU zSD?!HqZx95FO^EN}Bs~^hWJAD6csCoOne#q;OK+UT{*>M7DzdF>pp7i`4 z{2cm^pvJ%K`70zJ%{ZiO_N4Uo_>TcGUQ1-0IO&x26sI0O|(Q&8(X?%V57xRKAQHLLNtMM81LOohb)? z0Xc~jWw_b22a=bvg;AowbP49kh)Tz4jR5T z;}%@9vfjdVE1YIxyJun&&&OG6=Yy!e6xOrF!i-Ls78|t_Gu%$XrWwu~l041KaK|NV zW)7^*@Fy^!q*&%~-ZV&;PvnezUb?QQnwld)n%Uu6&85DcIv16#dAlBD z&P)c0q_)VnYbIUNz?Q48W-^Wpg8z@kneBvbi6Lt)J{ouE=#WW z!&bX#=33V^q-H7!8)0OovPDHj87MQ%iOtNlcV7)MQ+0)wF@yld8;M=SM$isF6L%7Y&jcnZ{0LZ&1~FW3wJ4N;V!K-O|{h+meL!s(u?4X zIm&FD1x+)XJ2N*kVdkRK5wFM0amRMJ;iAkOQy6PxQAy2lS2v4AV79_2&j`TO)#8ZP zHl4($TrI0i)GNpE5}`U|!=+}>H$7FEaoRDCDkI$Gkli-0ZChnd^M>TLuOP z2Kp2Pm04mnZ3b*PWUFzW1@-0gc28Q=+{29)A>Ch#Tl*LtHF6$NW!42Pztc+F5Ras9 za$<6H&62_Xfj-_)#+fR!-8M#K?o76|-3-ELx2-KIL1cU9X2&W!*T%^fGC^fDs>O8z zWXSHE53|1MW}XDi%2=GV(jgnQ3pL%lZMQ9}dpAXPpB>!1yYG;T6kD!dInW)l?QX+m z+P1&lwS)Z+^_{5r0Xpq;?CN1=xAzbB57JYr6?~A?+`-bmAzK>AwdJX8y3|HNCv32R zGR10RwtT+1d|qd2xE2TQ=?WUj^7-o~Eb9F-o$D-C^~ufFSmvtsB185JjEi?c5UlQf z?FJvZq#mUsDW_?`jo57EG-V>~-LVK%)*tSWMmt3sjh9~~gBIV(xa&xZMIK#S&m3KK zN>5eKHnEF%ise*2g=c%xYTdPS-q&={^`FD)AHk2shgX|8)z*^g@_AMwms)O=#HuH> zT3<`4%jY|0j1S40BlNm|E(@DsT3()AAir`ocruCe4sJi}@}Atf{0cd6yfoftR}x%8 zQVk`|rpv_0lz(XD&6V}ER7Nl{Tz)BW<>Tip|G%LXrp_+^v~&?!H&yD@C%ozeb|KfJ zj%4-`vejg)1eF8}3t`Ph{8e>wWn7}{4!-6A`h@LY lxiU8QLAXtr2{Ka@;pVlFFA{Yzw5)M&1d0#*tCKb-{2$?a*FgXP literal 4314 zcmai#ZH!!18OM)W-nK%qwg{qdwMq+RmfgNtwm`e>?6SLbyW8E_Qb1^LXU^_iI&<&z zzDzqG8p4MrqDf;ygbxIjG{mmNAQ3dt5YhQ$e1k+y)TDmV#26Dy)DI*^{r&GfyF1%T zyt&W)o#(th=Q+=L?xpo>zG8TeA)i1te8`xm;D@i~!*lJml;L&odUzB32>cA>HM{t% zgL~n1a0;sbBT)V3UHci>hyDe~66RTW6@1?5FG4N(61)apgqrW$PX9jSH9vOzDOCSo z!5iT3q5A&?YWzQ-*7=vC8Y!AJQ0><{eIt~;x4{i?E7Z7suDuMk?@1_oA9L-#qlIgz z4;(|Neu-=EK&^My>F1#OKjU~F$}i8tweYKs--Nv8yH0-#%C4V5&HuL3e*ABg|h2*cq7~lbzZxn{5s)y7;3$vj!!_1uetU*lz$>ue-^T& zc^dMXFY(d&JO{Pj%TVLL<@g%Z_&1==q3iUwq2{^d>i-NCXa98iRX9s_t%n+aGt|6y zINk-dUO&{ncR}rIuWKKL8b1NmZ`$!F3_oDZaVWq35~Ds2{|Gn2|3Iy`kA^$6KKMcpKEXEsldw^A17TJqk7cQP+M9>OAUDc4biZpLKj1s{c8t{d^J1 zzh8Fk&qIxS!RZ&A{tc*cuR^W=I@CIELG9<~j+fv~=d!;%<2k5(y$EIJ1*mmjh4SZjp#1ixtAEGU z{~BsPe}KAAe}!8A-%#VPVUyxosPXF{O=c5R+-!%Me;jI_S;%XS^Wmt?DJZ+1g6e+` zYQ7g7zXr9x3sCdC;`BFM`gLG?0W|i_PI~=FSrfS`g-n0rjgGfhmlVsOV2(E zw>xD9DrRNpJ;+|TV6k8L> z5yV5L5czo!xew7U~rWrF=G*1DU6)(LxbewCbEV>I($NF6shW*7ZUTZt(kfrd(9xsQerHz z)hOidOvllawwjj4Yo#Ojg=ie|g3}GZZ+fayIuKhqC@hsZz9Dbhz~HXZz>d=3Hg90} z(Dof$4~)!K=vbPuvhMx*-Me*QaA2TM@lnF;FlqSMHssAlS?brii{5=nUb6cZnuK?M zHEKRU?{FdGLX|4kZ#t8f>V~*5$3|!RCdVg7R;(WEAL!#sr8Z=lP78ZednUc7)$oIG zk5{cLHKg~?RYps@SNh2uazJS$tVT7$XUN-qB1rolw4vh3)=K+3Ltd-1U{g)j-|BdS z{X6@PmRtvIV>+_6Afkgu!8cza%I|G zY;+f8>Vj>ukDexfA?_|-F`%wX%WzIKpVeh6ugWy5>hqM>(=o~~197mt_T>xwzbW&0 zR32@U4cmD(jJiJ{5$4~?sAI`)c{;SDNnF>H9c#9g zagC<^j{8igy^N#s50^8xDy`(k?jkdh5 zuNTfBYbHva`UI\n" "Language-Team: iwla\n" -"Language: fr_FR\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Generated-By: pygettext.py 1.5\n" -"X-Generator: Poedit 1.6.10\n" +"X-Generator: Poedit 3.1.1\n" "X-Poedit-SourceCharset: UTF-8\n" #: display.py:32 @@ -37,13 +38,11 @@ msgstr "Juillet" msgid "March" msgstr "Mars" -#: display.py:32 -#: iwla.py:472 +#: display.py:32 iwla.py:503 msgid "June" msgstr "Juin" -#: display.py:32 -#: iwla.py:472 +#: display.py:32 iwla.py:503 msgid "May" msgstr "Mai" @@ -67,177 +66,152 @@ msgstr "Octobre" msgid "September" msgstr "Septembre" -#: display.py:195 +#: display.py:196 msgid "Ratio" msgstr "Pourcentage" -#: iwla.py:413 +#: iwla.py:446 msgid "Statistics" msgstr "Statistiques" -#: iwla.py:421 -#: iwla.py:474 +#: iwla.py:454 iwla.py:505 msgid "Not viewed Bandwidth" msgstr "Traffic non vu" -#: iwla.py:421 -#: iwla.py:474 +#: iwla.py:454 iwla.py:505 msgid "Visits" msgstr "Visites" -#: iwla.py:421 -#: iwla.py:474 -#: plugins/display/all_visits.py:70 -#: plugins/display/feeds.py:76 -#: plugins/display/hours_stats.py:73 -#: plugins/display/hours_stats.py:83 -#: plugins/display/referers.py:95 -#: plugins/display/referers.py:153 -#: plugins/display/top_downloads.py:97 -#: plugins/display/top_visitors.py:72 -#: plugins/display/track_users.py:113 -msgid "Hits" -msgstr "Hits" - -#: iwla.py:421 -#: iwla.py:474 -#: plugins/display/all_visits.py:70 -#: plugins/display/feeds.py:76 -#: plugins/display/hours_stats.py:73 -#: plugins/display/hours_stats.py:83 -#: plugins/display/referers.py:95 -#: plugins/display/referers.py:153 -#: plugins/display/top_visitors.py:72 -#: plugins/display/track_users.py:77 -#: plugins/display/track_users.py:113 +#: iwla.py:454 iwla.py:505 plugins/display/all_visits.py:70 +#: plugins/display/feeds.py:76 plugins/display/filter_users.py:77 +#: plugins/display/filter_users.py:113 plugins/display/hours_stats.py:73 +#: plugins/display/hours_stats.py:83 plugins/display/referers.py:95 +#: plugins/display/referers.py:153 plugins/display/top_visitors.py:72 msgid "Pages" msgstr "Pages" -#: iwla.py:421 -#: iwla.py:474 -#: plugins/display/all_visits.py:70 -#: plugins/display/hours_stats.py:73 -#: plugins/display/hours_stats.py:83 +#: iwla.py:454 iwla.py:505 plugins/display/all_visits.py:70 +#: plugins/display/feeds.py:76 plugins/display/filter_users.py:113 +#: plugins/display/hours_stats.py:73 plugins/display/hours_stats.py:83 +#: plugins/display/referers.py:95 plugins/display/referers.py:153 +#: plugins/display/top_downloads.py:97 plugins/display/top_visitors.py:72 +msgid "Hits" +msgstr "Hits" + +#: iwla.py:454 iwla.py:505 plugins/display/all_visits.py:70 +#: plugins/display/hours_stats.py:73 plugins/display/hours_stats.py:83 +#: plugins/display/robot_bandwidth.py:81 plugins/display/robot_bandwidth.py:106 #: plugins/display/top_visitors.py:72 msgid "Bandwidth" msgstr "Bande passante" -#: iwla.py:421 -#: plugins/display/hours_stats.py:71 +#: iwla.py:454 plugins/display/hours_stats.py:71 msgid "By day" msgstr "Par jour" -#: iwla.py:421 -#: plugins/display/hours_stats.py:73 +#: iwla.py:454 plugins/display/hours_stats.py:73 msgid "Day" msgstr "Jour" -#: iwla.py:458 +#: iwla.py:493 msgid "Average" msgstr "Moyenne" -#: iwla.py:463 -#: iwla.py:508 +#: iwla.py:496 iwla.py:541 msgid "Total" msgstr "Total" -#: iwla.py:472 +#: iwla.py:503 msgid "Apr" msgstr "Avr" -#: iwla.py:472 +#: iwla.py:503 msgid "Aug" msgstr "Août" -#: iwla.py:472 +#: iwla.py:503 msgid "Dec" msgstr "Déc" -#: iwla.py:472 +#: iwla.py:503 msgid "Feb" msgstr "Fév" -#: iwla.py:472 +#: iwla.py:503 msgid "Jan" msgstr "Jan" -#: iwla.py:472 +#: iwla.py:503 msgid "Jul" msgstr "Jui" -#: iwla.py:472 +#: iwla.py:503 msgid "Mar" msgstr "Mars" -#: iwla.py:472 +#: iwla.py:503 msgid "Nov" msgstr "Nov" -#: iwla.py:472 +#: iwla.py:503 msgid "Oct" msgstr "Oct" -#: iwla.py:472 +#: iwla.py:503 msgid "Sep" msgstr "Sep" -#: iwla.py:473 +#: iwla.py:504 msgid "Summary" msgstr "Résumé" -#: iwla.py:474 +#: iwla.py:505 msgid "Month" msgstr "Mois" -#: iwla.py:474 -#: iwla.py:486 -#: plugins/display/feeds.py:99 -#: plugins/display/ip_to_geo.py:109 +#: iwla.py:505 iwla.py:517 plugins/display/feeds.py:101 +#: plugins/display/filter_users.py:108 plugins/display/ip_to_geo.py:109 #: plugins/display/operating_systems.py:90 -#: plugins/display/track_users.py:108 msgid "Details" msgstr "Détails" -#: iwla.py:474 -#: plugins/display/ip_to_geo.py:96 -#: plugins/display/ip_to_geo.py:114 +#: iwla.py:505 plugins/display/ip_to_geo.py:96 plugins/display/ip_to_geo.py:114 msgid "Visitors" msgstr "Visiteurs" -#: iwla.py:522 +#: iwla.py:553 msgid "Statistics for" msgstr "Statistiques pour" -#: iwla.py:529 +#: iwla.py:560 msgid "Last update" msgstr "Dernière mise à jour" -#: iwla.py:533 +#: iwla.py:564 msgid "Time analysis" msgstr "Durée de l'analyse" -#: iwla.py:535 +#: iwla.py:566 msgid "hours" -msgstr "heures " +msgstr "heures" -#: iwla.py:536 +#: iwla.py:567 msgid "minutes" msgstr "minutes" -#: iwla.py:536 +#: iwla.py:567 msgid "seconds" msgstr "secondes" -#: plugins/display/all_visits.py:70 -#: plugins/display/feeds.py:76 -#: plugins/display/ip_to_geo.py:64 +#: plugins/display/all_visits.py:70 plugins/display/feeds.py:76 +#: plugins/display/filter_users.py:113 plugins/display/ip_to_geo.py:64 +#: plugins/display/robot_bandwidth.py:81 plugins/display/robot_bandwidth.py:106 #: plugins/display/top_visitors.py:72 -#: plugins/display/track_users.py:113 msgid "Host" msgstr "Hôte" -#: plugins/display/all_visits.py:70 -#: plugins/display/top_visitors.py:72 +#: plugins/display/all_visits.py:70 plugins/display/robot_bandwidth.py:81 +#: plugins/display/robot_bandwidth.py:106 plugins/display/top_visitors.py:72 msgid "Last seen" msgstr "Dernière visite" @@ -245,8 +219,7 @@ msgstr "Dernière visite" msgid "All visits" msgstr "Toutes les visites" -#: plugins/display/all_visits.py:93 -#: plugins/display/top_visitors.py:72 +#: plugins/display/all_visits.py:93 plugins/display/top_visitors.py:72 msgid "Top visitors" msgstr "Top visiteurs" @@ -254,50 +227,39 @@ msgstr "Top visiteurs" msgid "Browsers" msgstr "Navigateurs" -#: plugins/display/browsers.py:79 -#: plugins/display/browsers.py:113 +#: plugins/display/browsers.py:79 plugins/display/browsers.py:114 msgid "Browser" msgstr "Navigateur" -#: plugins/display/browsers.py:79 -#: plugins/display/browsers.py:113 +#: plugins/display/browsers.py:79 plugins/display/browsers.py:114 #: plugins/display/operating_systems.py:78 -#: plugins/display/operating_systems.py:95 -#: plugins/display/top_hits.py:71 -#: plugins/display/top_hits.py:97 -#: plugins/display/top_pages.py:71 +#: plugins/display/operating_systems.py:95 plugins/display/top_hits.py:71 +#: plugins/display/top_hits.py:97 plugins/display/top_pages.py:71 #: plugins/display/top_pages.py:96 msgid "Entrance" msgstr "Entrées" -#: plugins/display/browsers.py:98 -#: plugins/display/browsers.py:128 -#: plugins/display/referers.py:110 -#: plugins/display/referers.py:125 -#: plugins/display/referers.py:140 -#: plugins/display/referers.py:163 -#: plugins/display/referers.py:174 -#: plugins/display/referers.py:185 -#: plugins/display/referers.py:222 -#: plugins/display/top_downloads.py:83 -#: plugins/display/top_downloads.py:103 -#: plugins/display/top_hits.py:82 -#: plugins/display/top_hits.py:103 -#: plugins/display/top_pages.py:82 -#: plugins/display/top_pages.py:102 +#: plugins/display/browsers.py:99 plugins/display/browsers.py:130 +#: plugins/display/filter_users.py:123 plugins/display/referers.py:110 +#: plugins/display/referers.py:125 plugins/display/referers.py:140 +#: plugins/display/referers.py:163 plugins/display/referers.py:174 +#: plugins/display/referers.py:185 plugins/display/referers.py:222 +#: plugins/display/top_downloads.py:83 plugins/display/top_downloads.py:103 +#: plugins/display/top_hits.py:82 plugins/display/top_hits.py:103 +#: plugins/display/top_pages.py:82 plugins/display/top_pages.py:102 #: plugins/display/top_visitors.py:92 msgid "Others" msgstr "Autres" -#: plugins/display/browsers.py:105 +#: plugins/display/browsers.py:106 msgid "Top Browsers" msgstr "Top Navigateurs" -#: plugins/display/browsers.py:107 +#: plugins/display/browsers.py:108 msgid "All Browsers" msgstr "Tous les navigateurs" -#: plugins/display/browsers.py:123 +#: plugins/display/browsers.py:125 plugins/display/filter_users.py:80 msgid "Unknown" msgstr "Inconnu" @@ -309,18 +271,30 @@ msgstr "Tous les agrégateurs" msgid "All feeds parsers" msgstr "Tous les agrégateurs" -#: plugins/display/feeds.py:92 +#: plugins/display/feeds.py:94 msgid "Merged feeds parsers" msgstr "Agrégateurs fusionnés" -#: plugins/display/feeds.py:97 +#: plugins/display/feeds.py:99 msgid "Feeds parsers" msgstr "Agrégateurs" -#: plugins/display/feeds.py:104 +#: plugins/display/feeds.py:106 msgid "Found" msgstr "Trouvé" +#: plugins/display/filter_users.py:77 +msgid "User Agent" +msgstr "Navigateur" + +#: plugins/display/filter_users.py:77 plugins/display/filter_users.py:106 +msgid "Filtered users" +msgstr "Utilisateurs filtrés" + +#: plugins/display/filter_users.py:77 plugins/display/filter_users.py:113 +msgid "Last Access" +msgstr "Dernière visite" + #: plugins/display/hours_stats.py:72 msgid "Fri" msgstr "Jeu" @@ -361,8 +335,7 @@ msgstr "Heures" msgid "Country" msgstr "Pays" -#: plugins/display/ip_to_geo.py:96 -#: plugins/display/ip_to_geo.py:107 +#: plugins/display/ip_to_geo.py:96 plugins/display/ip_to_geo.py:107 #: plugins/display/ip_to_geo.py:114 msgid "Countries" msgstr "Pays" @@ -381,23 +354,19 @@ msgstr "Système d'exploitation" msgid "Connexion from" msgstr "Connexion depuis" -#: plugins/display/referers.py:95 -#: plugins/display/referers.py:153 +#: plugins/display/referers.py:95 plugins/display/referers.py:153 msgid "Origin" msgstr "Origine" -#: plugins/display/referers.py:99 -#: plugins/display/referers.py:156 +#: plugins/display/referers.py:99 plugins/display/referers.py:156 msgid "Search Engine" msgstr "Moteur de recherche" -#: plugins/display/referers.py:114 -#: plugins/display/referers.py:167 +#: plugins/display/referers.py:114 plugins/display/referers.py:167 msgid "External URL" msgstr "URL externe" -#: plugins/display/referers.py:129 -#: plugins/display/referers.py:178 +#: plugins/display/referers.py:129 plugins/display/referers.py:178 msgid "External URL (robot)" msgstr "URL externe (robot)" @@ -413,18 +382,15 @@ msgstr "Toutes les origines" msgid "All Key Phrases" msgstr "Toutes les phrases clé" -#: plugins/display/referers.py:200 -#: plugins/display/referers.py:216 +#: plugins/display/referers.py:200 plugins/display/referers.py:216 msgid "Key phrase" msgstr "Phrase clé" -#: plugins/display/referers.py:200 -#: plugins/display/referers.py:216 +#: plugins/display/referers.py:200 plugins/display/referers.py:216 msgid "Search" msgstr "Recherche" -#: plugins/display/referers.py:200 -#: plugins/display/referers_diff.py:56 +#: plugins/display/referers.py:200 plugins/display/referers_diff.py:56 msgid "Key phrases" msgstr "Phrases clé" @@ -436,22 +402,26 @@ msgstr "Top phrases clé" msgid "All key phrases" msgstr "Toutes les phrases clé" +#: plugins/display/robot_bandwidth.py:99 +msgid "Robots bandwidth" +msgstr "Bande passante robots" + +#: plugins/display/robot_bandwidth.py:101 +msgid "All robots bandwidth" +msgstr "Bande passante tous les robots" + #: plugins/display/top_downloads.py:71 msgid "Hit" msgstr "Hit" -#: plugins/display/top_downloads.py:71 -#: plugins/display/top_downloads.py:91 +#: plugins/display/top_downloads.py:71 plugins/display/top_downloads.py:91 #: plugins/display/top_downloads_diff.py:56 msgid "All Downloads" msgstr "Tous les téléchargements" -#: plugins/display/top_downloads.py:71 -#: plugins/display/top_downloads.py:97 -#: plugins/display/top_hits.py:71 -#: plugins/display/top_hits.py:97 -#: plugins/display/top_pages.py:71 -#: plugins/display/top_pages.py:96 +#: plugins/display/top_downloads.py:71 plugins/display/top_downloads.py:97 +#: plugins/display/top_hits.py:71 plugins/display/top_hits.py:97 +#: plugins/display/top_pages.py:71 plugins/display/top_pages.py:96 msgid "URI" msgstr "URI" @@ -459,13 +429,12 @@ msgstr "URI" msgid "Top Downloads" msgstr "Top Téléchargements" -#: plugins/display/top_hits.py:71 -#: plugins/display/top_hits.py:91 +#: plugins/display/top_hits.py:71 plugins/display/top_hits.py:91 msgid "All Hits" msgstr "Tous les hits" -#: plugins/display/top_pages.py:71 -#: plugins/display/top_pages.py:90 +#: plugins/display/top_pages.py:71 plugins/display/top_pages.py:90 +#: plugins/display/top_pages_diff.py:56 msgid "All Pages" msgstr "Toutes les pages" @@ -473,16 +442,6 @@ msgstr "Toutes les pages" msgid "Top Pages" msgstr "Top Pages" -#: plugins/display/track_users.py:77 -#: plugins/display/track_users.py:106 -msgid "Tracked users" -msgstr "Utilisateurs traqués" - -#: plugins/display/track_users.py:77 -#: plugins/display/track_users.py:113 -msgid "Last Access" -msgstr "Dernière visite" - #~ msgid "IP" #~ msgstr "IP" diff --git a/tools/gettext.sh b/tools/gettext.sh index 81bafab..9155059 100755 --- a/tools/gettext.sh +++ b/tools/gettext.sh @@ -1,3 +1,5 @@ #!/bin/sh -pygettext -a -d iwla -o iwla.pot -k gettext *.py plugins/pre_analysis/*.py plugins/post_analysis/*.py plugins/display/*.py +./tools/pygettext.py -a -d iwla -o iwla.pot -k gettext *.py plugins/pre_analysis/*.py plugins/post_analysis/*.py plugins/display/*.py +# Synchronize with new generated iwla.pot at root +find locales -name '*.po' -exec poedit \{\} \; diff --git a/tools/pygettext.py b/tools/pygettext.py new file mode 100755 index 0000000..7ada791 --- /dev/null +++ b/tools/pygettext.py @@ -0,0 +1,684 @@ +#! /usr/bin/env python3 +# -*- coding: iso-8859-1 -*- +# Originally written by Barry Warsaw +# +# Minimally patched to make it even more xgettext compatible +# by Peter Funk +# +# 2002-11-22 Jürgen Hermann +# Added checks that _() only contains string literals, and +# command line args are resolved to module lists, i.e. you +# can now pass a filename, a module or package name, or a +# directory (including globbing chars, important for Win32). +# Made docstring fit in 80 chars wide displays using pydoc. +# + +# for selftesting +try: + import fintl + _ = fintl.gettext +except ImportError: + _ = lambda s: s + +__doc__ = _("""pygettext -- Python equivalent of xgettext(1) + +Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the +internationalization of C programs. Most of these tools are independent of +the programming language and can be used from within Python programs. +Martin von Loewis' work[1] helps considerably in this regard. + +There's one problem though; xgettext is the program that scans source code +looking for message strings, but it groks only C (or C++). Python +introduces a few wrinkles, such as dual quoting characters, triple quoted +strings, and raw strings. xgettext understands none of this. + +Enter pygettext, which uses Python's standard tokenize module to scan +Python source code, generating .pot files identical to what GNU xgettext[2] +generates for C and C++ code. From there, the standard GNU tools can be +used. + +A word about marking Python strings as candidates for translation. GNU +xgettext recognizes the following keywords: gettext, dgettext, dcgettext, +and gettext_noop. But those can be a lot of text to include all over your +code. C and C++ have a trick: they use the C preprocessor. Most +internationalized C source includes a #define for gettext() to _() so that +what has to be written in the source is much less. Thus these are both +translatable strings: + + gettext("Translatable String") + _("Translatable String") + +Python of course has no preprocessor so this doesn't work so well. Thus, +pygettext searches only for _() by default, but see the -k/--keyword flag +below for how to augment this. + + [1] https://www.python.org/workshops/1997-10/proceedings/loewis.html + [2] https://www.gnu.org/software/gettext/gettext.html + +NOTE: pygettext attempts to be option and feature compatible with GNU +xgettext where ever possible. However some options are still missing or are +not fully implemented. Also, xgettext's use of command line switches with +option arguments is broken, and in these cases, pygettext just defines +additional switches. + +Usage: pygettext [options] inputfile ... + +Options: + + -a + --extract-all + Extract all strings. + + -d name + --default-domain=name + Rename the default output file from messages.pot to name.pot. + + -E + --escape + Replace non-ASCII characters with octal escape sequences. + + -D + --docstrings + Extract module, class, method, and function docstrings. These do + not need to be wrapped in _() markers, and in fact cannot be for + Python to consider them docstrings. (See also the -X option). + + -h + --help + Print this help message and exit. + + -k word + --keyword=word + Keywords to look for in addition to the default set, which are: + %(DEFAULTKEYWORDS)s + + You can have multiple -k flags on the command line. + + -K + --no-default-keywords + Disable the default set of keywords (see above). Any keywords + explicitly added with the -k/--keyword option are still recognized. + + --no-location + Do not write filename/lineno location comments. + + -n + --add-location + Write filename/lineno location comments indicating where each + extracted string is found in the source. These lines appear before + each msgid. The style of comments is controlled by the -S/--style + option. This is the default. + + -o filename + --output=filename + Rename the default output file from messages.pot to filename. If + filename is `-' then the output is sent to standard out. + + -p dir + --output-dir=dir + Output files will be placed in directory dir. + + -S stylename + --style stylename + Specify which style to use for location comments. Two styles are + supported: + + Solaris # File: filename, line: line-number + GNU #: filename:line + + The style name is case insensitive. GNU style is the default. + + -v + --verbose + Print the names of the files being processed. + + -V + --version + Print the version of pygettext and exit. + + -w columns + --width=columns + Set width of output to columns. + + -x filename + --exclude-file=filename + Specify a file that contains a list of strings that are not be + extracted from the input files. Each string to be excluded must + appear on a line by itself in the file. + + -X filename + --no-docstrings=filename + Specify a file that contains a list of files (one per line) that + should not have their docstrings extracted. This is only useful in + conjunction with the -D option above. + +If `inputfile' is -, standard input is read. +""") + +import os +import importlib.machinery +import importlib.util +import sys +import glob +import time +import getopt +import ast +import token +import tokenize + +__version__ = '1.5' + +default_keywords = ['_'] +DEFAULTKEYWORDS = ', '.join(default_keywords) + +EMPTYSTRING = '' + + + +# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's +# there. +pot_header = _('''\ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\\n" +"POT-Creation-Date: %(time)s\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME \\n" +"Language-Team: LANGUAGE \\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=%(charset)s\\n" +"Content-Transfer-Encoding: %(encoding)s\\n" +"Generated-By: pygettext.py %(version)s\\n" + +''') + + +def usage(code, msg=''): + print(__doc__ % globals(), file=sys.stderr) + if msg: + print(msg, file=sys.stderr) + sys.exit(code) + + + +def make_escapes(pass_nonascii): + global escapes, escape + if pass_nonascii: + # Allow non-ascii characters to pass through so that e.g. 'msgid + # "Höhe"' would result not result in 'msgid "H\366he"'. Otherwise we + # escape any character outside the 32..126 range. + mod = 128 + escape = escape_ascii + else: + mod = 256 + escape = escape_nonascii + escapes = [r"\%03o" % i for i in range(mod)] + for i in range(32, 127): + escapes[i] = chr(i) + escapes[ord('\\')] = r'\\' + escapes[ord('\t')] = r'\t' + escapes[ord('\r')] = r'\r' + escapes[ord('\n')] = r'\n' + escapes[ord('\"')] = r'\"' + + +def escape_ascii(s, encoding): + return ''.join(escapes[ord(c)] if ord(c) < 128 else c for c in s) + +def escape_nonascii(s, encoding): + return ''.join(escapes[b] for b in s.encode(encoding)) + + +def is_literal_string(s): + return s[0] in '\'"' or (s[0] in 'rRuU' and s[1] in '\'"') + + +def safe_eval(s): + # unwrap quotes, safely + return eval(s, {'__builtins__':{}}, {}) + + +def normalize(s, encoding): + # This converts the various Python string types into a format that is + # appropriate for .po files, namely much closer to C style. + lines = s.split('\n') + if len(lines) == 1: + s = '"' + escape(s, encoding) + '"' + else: + if not lines[-1]: + del lines[-1] + lines[-1] = lines[-1] + '\n' + for i in range(len(lines)): + lines[i] = escape(lines[i], encoding) + lineterm = '\\n"\n"' + s = '""\n"' + lineterm.join(lines) + '"' + return s + + +def containsAny(str, set): + """Check whether 'str' contains ANY of the chars in 'set'""" + return 1 in [c in str for c in set] + + +def getFilesForName(name): + """Get a list of module files for a filename, a module or package name, + or a directory. + """ + if not os.path.exists(name): + # check for glob chars + if containsAny(name, "*?[]"): + files = glob.glob(name) + list = [] + for file in files: + list.extend(getFilesForName(file)) + return list + + # try to find module or package + try: + spec = importlib.util.find_spec(name) + name = spec.origin + except ImportError: + name = None + if not name: + return [] + + if os.path.isdir(name): + # find all python files in directory + list = [] + # get extension for python source files + _py_ext = importlib.machinery.SOURCE_SUFFIXES[0] + for root, dirs, files in os.walk(name): + # don't recurse into CVS directories + if 'CVS' in dirs: + dirs.remove('CVS') + # add all *.py files to list + list.extend( + [os.path.join(root, file) for file in files + if os.path.splitext(file)[1] == _py_ext] + ) + return list + elif os.path.exists(name): + # a single file + return [name] + + return [] + + +class TokenEater: + def __init__(self, options): + self.__options = options + self.__messages = {} + self.__state = self.__waiting + self.__data = [] + self.__lineno = -1 + self.__freshmodule = 1 + self.__curfile = None + self.__enclosurecount = 0 + + def __call__(self, ttype, tstring, stup, etup, line): + # dispatch +## import token +## print('ttype:', token.tok_name[ttype], 'tstring:', tstring, +## file=sys.stderr) + self.__state(ttype, tstring, stup[0]) + + def __waiting(self, ttype, tstring, lineno): + opts = self.__options + # Do docstring extractions, if enabled + if opts.docstrings and not opts.nodocstrings.get(self.__curfile): + # module docstring? + if self.__freshmodule: + if ttype == tokenize.STRING and is_literal_string(tstring): + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__freshmodule = 0 + return + if ttype in (tokenize.COMMENT, tokenize.NL, tokenize.ENCODING): + return + self.__freshmodule = 0 + # class or func/method docstring? + if ttype == tokenize.NAME and tstring in ('class', 'def'): + self.__state = self.__suiteseen + return + if ttype == tokenize.NAME and tstring in opts.keywords: + self.__state = self.__keywordseen + return + if ttype == tokenize.STRING: + maybe_fstring = ast.parse(tstring, mode='eval').body + if not isinstance(maybe_fstring, ast.JoinedStr): + return + for value in filter(lambda node: isinstance(node, ast.FormattedValue), + maybe_fstring.values): + for call in filter(lambda node: isinstance(node, ast.Call), + ast.walk(value)): + func = call.func + if isinstance(func, ast.Name): + func_name = func.id + elif isinstance(func, ast.Attribute): + func_name = func.attr + else: + continue + + if func_name not in opts.keywords: + continue + if len(call.args) != 1: + print(_( + '*** %(file)s:%(lineno)s: Seen unexpected amount of' + ' positional arguments in gettext call: %(source_segment)s' + ) % { + 'source_segment': ast.get_source_segment(tstring, call) or tstring, + 'file': self.__curfile, + 'lineno': lineno + }, file=sys.stderr) + continue + if call.keywords: + print(_( + '*** %(file)s:%(lineno)s: Seen unexpected keyword arguments' + ' in gettext call: %(source_segment)s' + ) % { + 'source_segment': ast.get_source_segment(tstring, call) or tstring, + 'file': self.__curfile, + 'lineno': lineno + }, file=sys.stderr) + continue + arg = call.args[0] + if not isinstance(arg, ast.Constant): + print(_( + '*** %(file)s:%(lineno)s: Seen unexpected argument type' + ' in gettext call: %(source_segment)s' + ) % { + 'source_segment': ast.get_source_segment(tstring, call) or tstring, + 'file': self.__curfile, + 'lineno': lineno + }, file=sys.stderr) + continue + if isinstance(arg.value, str): + self.__addentry(arg.value, lineno) + + def __suiteseen(self, ttype, tstring, lineno): + # skip over any enclosure pairs until we see the colon + if ttype == tokenize.OP: + if tstring == ':' and self.__enclosurecount == 0: + # we see a colon and we're not in an enclosure: end of def + self.__state = self.__suitedocstring + elif tstring in '([{': + self.__enclosurecount += 1 + elif tstring in ')]}': + self.__enclosurecount -= 1 + + def __suitedocstring(self, ttype, tstring, lineno): + # ignore any intervening noise + if ttype == tokenize.STRING and is_literal_string(tstring): + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__state = self.__waiting + elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, + tokenize.COMMENT): + # there was no class docstring + self.__state = self.__waiting + + def __keywordseen(self, ttype, tstring, lineno): + if ttype == tokenize.OP and tstring == '(': + self.__data = [] + self.__lineno = lineno + self.__state = self.__openseen + else: + self.__state = self.__waiting + + def __openseen(self, ttype, tstring, lineno): + if ttype == tokenize.OP and tstring == ')': + # We've seen the last of the translatable strings. Record the + # line number of the first line of the strings and update the list + # of messages seen. Reset state for the next batch. If there + # were no strings inside _(), then just ignore this entry. + if self.__data: + self.__addentry(EMPTYSTRING.join(self.__data)) + self.__state = self.__waiting + elif ttype == tokenize.STRING and is_literal_string(tstring): + self.__data.append(safe_eval(tstring)) + elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT, + token.NEWLINE, tokenize.NL]: + # warn if we see anything else than STRING or whitespace + print(_( + '*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"' + ) % { + 'token': tstring, + 'file': self.__curfile, + 'lineno': self.__lineno + }, file=sys.stderr) + self.__state = self.__waiting + + def __addentry(self, msg, lineno=None, isdocstring=0): + if lineno is None: + lineno = self.__lineno + if not msg in self.__options.toexclude: + entry = (self.__curfile, lineno) + self.__messages.setdefault(msg, {})[entry] = isdocstring + + def set_filename(self, filename): + self.__curfile = filename + self.__freshmodule = 1 + + def write(self, fp): + options = self.__options + timestamp = time.strftime('%Y-%m-%d %H:%M%z') + encoding = fp.encoding if fp.encoding else 'UTF-8' + print(pot_header % {'time': timestamp, 'version': __version__, + 'charset': encoding, + 'encoding': '8bit'}, file=fp) + # Sort the entries. First sort each particular entry's keys, then + # sort all the entries by their first item. + reverse = {} + for k, v in self.__messages.items(): + keys = sorted(v.keys()) + reverse.setdefault(tuple(keys), []).append((k, v)) + rkeys = sorted(reverse.keys()) + for rkey in rkeys: + rentries = reverse[rkey] + rentries.sort() + for k, v in rentries: + # If the entry was gleaned out of a docstring, then add a + # comment stating so. This is to aid translators who may wish + # to skip translating some unimportant docstrings. + isdocstring = any(v.values()) + # k is the message string, v is a dictionary-set of (filename, + # lineno) tuples. We want to sort the entries in v first by + # file name and then by line number. + v = sorted(v.keys()) + if not options.writelocations: + pass + # location comments are different b/w Solaris and GNU: + elif options.locationstyle == options.SOLARIS: + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + print(_( + '# File: %(filename)s, line: %(lineno)d') % d, file=fp) + elif options.locationstyle == options.GNU: + # fit as many locations on one line, as long as the + # resulting line length doesn't exceed 'options.width' + locline = '#:' + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + s = _(' %(filename)s:%(lineno)d') % d + if len(locline) + len(s) <= options.width: + locline = locline + s + else: + print(locline, file=fp) + locline = "#:" + s + if len(locline) > 2: + print(locline, file=fp) + if isdocstring: + print('#, docstring', file=fp) + print('msgid', normalize(k, encoding), file=fp) + print('msgstr ""\n', file=fp) + + + +def main(): + global default_keywords + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ad:DEhk:Kno:p:S:Vvw:x:X:', + ['extract-all', 'default-domain=', 'escape', 'help', + 'keyword=', 'no-default-keywords', + 'add-location', 'no-location', 'output=', 'output-dir=', + 'style=', 'verbose', 'version', 'width=', 'exclude-file=', + 'docstrings', 'no-docstrings', + ]) + except getopt.error as msg: + usage(1, msg) + + # for holding option values + class Options: + # constants + GNU = 1 + SOLARIS = 2 + # defaults + extractall = 0 # FIXME: currently this option has no effect at all. + escape = 0 + keywords = [] + outpath = '' + outfile = 'messages.pot' + writelocations = 1 + locationstyle = GNU + verbose = 0 + width = 78 + excludefilename = '' + docstrings = 0 + nodocstrings = {} + + options = Options() + locations = {'gnu' : options.GNU, + 'solaris' : options.SOLARIS, + } + + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--extract-all'): + options.extractall = 1 + elif opt in ('-d', '--default-domain'): + options.outfile = arg + '.pot' + elif opt in ('-E', '--escape'): + options.escape = 1 + elif opt in ('-D', '--docstrings'): + options.docstrings = 1 + elif opt in ('-k', '--keyword'): + options.keywords.append(arg) + elif opt in ('-K', '--no-default-keywords'): + default_keywords = [] + elif opt in ('-n', '--add-location'): + options.writelocations = 1 + elif opt in ('--no-location',): + options.writelocations = 0 + elif opt in ('-S', '--style'): + options.locationstyle = locations.get(arg.lower()) + if options.locationstyle is None: + usage(1, _('Invalid value for --style: %s') % arg) + elif opt in ('-o', '--output'): + options.outfile = arg + elif opt in ('-p', '--output-dir'): + options.outpath = arg + elif opt in ('-v', '--verbose'): + options.verbose = 1 + elif opt in ('-V', '--version'): + print(_('pygettext.py (xgettext for Python) %s') % __version__) + sys.exit(0) + elif opt in ('-w', '--width'): + try: + options.width = int(arg) + except ValueError: + usage(1, _('--width argument must be an integer: %s') % arg) + elif opt in ('-x', '--exclude-file'): + options.excludefilename = arg + elif opt in ('-X', '--no-docstrings'): + fp = open(arg) + try: + while 1: + line = fp.readline() + if not line: + break + options.nodocstrings[line[:-1]] = 1 + finally: + fp.close() + + # calculate escapes + make_escapes(not options.escape) + + # calculate all keywords + options.keywords.extend(default_keywords) + + # initialize list of strings to exclude + if options.excludefilename: + try: + with open(options.excludefilename) as fp: + options.toexclude = fp.readlines() + except IOError: + print(_( + "Can't read --exclude-file: %s") % options.excludefilename, file=sys.stderr) + sys.exit(1) + else: + options.toexclude = [] + + # resolve args to module lists + expanded = [] + for arg in args: + if arg == '-': + expanded.append(arg) + else: + expanded.extend(getFilesForName(arg)) + args = expanded + + # slurp through all the files + eater = TokenEater(options) + for filename in args: + if filename == '-': + if options.verbose: + print(_('Reading standard input')) + fp = sys.stdin.buffer + closep = 0 + else: + if options.verbose: + print(_('Working on %s') % filename) + fp = open(filename, 'rb') + closep = 1 + try: + eater.set_filename(filename) + try: + tokens = tokenize.tokenize(fp.readline) + for _token in tokens: + eater(*_token) + except tokenize.TokenError as e: + print('%s: %s, line %d, column %d' % ( + e.args[0], filename, e.args[1][0], e.args[1][1]), + file=sys.stderr) + finally: + if closep: + fp.close() + + # write the output + if options.outfile == '-': + fp = sys.stdout + closep = 0 + else: + if options.outpath: + options.outfile = os.path.join(options.outpath, options.outfile) + fp = open(options.outfile, 'w') + closep = 1 + try: + eater.write(fp) + finally: + if closep: + fp.close() + + +if __name__ == '__main__': + main() + # some more test strings + # this one creates a warning + _('*** Seen unexpected token "%(token)s"') % {'token': 'test'} + _('more' 'than' 'one' 'string')