158857c3aaa1e749ed25b8893dc7e5925daff965
[ikiwiki.git] / doc / patchqueue / locale_patch.mdwn
1 From [[Faidon]]:
2
3 Joey,
4 Attached is a patch that adds locale support to ikiwiki.
5 A suitable locale is choosed in that order:
6
7 1. $config{locale}
8 2. $ENV{LC_ALL}
9 3. en_US.UTF-8
10 4. en_*.UTF-8
11 5. *.UTF-8
12 6. en_US
13 7. en_*
14 8. *
15 9. POSIX
16 (where * == the first item found)
17
18 The patch brings the following functionality:
19
20 1. Proper local time, either using a UTF-8 locale or not (by the means
21 of a new function decode_locale),
22 2. Support for UTF-8 (or ISO-8859-X) filenames in SVN. Before this
23 patch, commiting (or even rcs_updating) on repositories with UTF-8
24 filenames is impossible.
25
26 This is RFC because it has some hard-coded parts: 'locale -a' and
27 /usr/share/i18n/SUPPORTED. They obviously work on Debian, but I'm sure
28 they won't work on other distros, let along on other operating systems.
29
30 Besides that, it's quite a big of a change and I could use some comments
31 to make it better :)
32
33 ----
34
35 First comments on this: 
36
37 * Defaulting to en_US anything or even en feels wrong. Defaulting to C is standard.
38 * If ikiwiki uses utf-8, why should it cater to non-utf8 locales? If it only supports locales that are utf-8 or simple ascii then it doesn't need to do messy charset conversion and charset determininition via the SUPPORTED file. It can just make sure that incoming data is properly interpreted as utf-8 by perl; based on the patch I guess there are still some issues along those lines in the svn filename code.
39 * I don't see any real need to guess at a locale to use with locale -a. If a user wants a locale they should set one. (_Or_, ikiwiki could record the user's own locale settings at wiki setup time, so that the compiled CGI wrapper contains the locale settings in effect when it was built. However, this is likely to have issues with ikiwiki-mass-rebuild. Maybe some kind of tool to generate a setup file, including a locale setting taken from the user's locale would be a useful avenue..)
40
41 --[[Joey]]
42
43 ----
44
45         Index: IkiWiki/Rcs/svn.pm
46         ===================================================================
47         --- IkiWiki/Rcs/svn.pm  (revision 904)
48         +++ IkiWiki/Rcs/svn.pm  (working copy)
49         @@ -174,16 +236,16 @@
50                 }
51                 my $rev=int(possibly_foolish_untaint($ENV{REV}));
52                 
53         -       my $user=`svnlook author $config{svnrepo} -r $rev`;
54         +       my $user=decode_locale(`svnlook author $config{svnrepo} -r $rev`);
55                 chomp $user;
56         -       my $message=`svnlook log $config{svnrepo} -r $rev`;
57         +       my $message=decode_locale(`svnlook log $config{svnrepo} -r $rev`);
58                 if ($message=~/$svn_webcommit/) {
59                         $user="$1";
60                         $message=$2;
61                 }
62          
63                 my @changed_pages;
64         -       foreach my $change (`svnlook changed $config{svnrepo} -r $rev`) {
65         +       foreach my $change (decode_locale(`svnlook changed $config{svnrepo} -r $rev`)) {
66                         chomp $change;
67                         if ($change =~ /^[A-Z]+\s+\Q$config{svnpath}\E\/(.*)/) {
68                                 push @changed_pages, $1;
69         @@ -197,7 +259,7 @@
70                         # subscribers a diff that might contain pages they did not
71                         # sign up for. Should separate the diff per page and
72                         # reassemble into one mail with just the pages subscribed to.
73         -               my $diff=`svnlook diff $config{svnrepo} -r $rev --no-diff-deleted`;
74         +               my $diff=decode_locale(`svnlook diff $config{svnrepo} -r $rev --no-diff-deleted`);
75          
76                         my $subject="$config{wikiname} update of ";
77                         if (@changed_pages > 2) {
78         Index: IkiWiki/Render.pm
79         ===================================================================
80         --- IkiWiki/Render.pm   (revision 904)
81         +++ IkiWiki/Render.pm   (working copy)
82         @@ -222,7 +222,7 @@
83                 eval q{use POSIX};
84                 # strftime doesn't know about encodings, so make sure
85                 # its output is properly treated as utf8
86         -       return decode_utf8(POSIX::strftime(
87         +       return decode_locale(POSIX::strftime(
88                                 $config{timeformat}, localtime($time)));
89          } #}}}
90          
91         Index: IkiWiki.pm
92         ===================================================================
93         --- IkiWiki.pm  (revision 904)
94         +++ IkiWiki.pm  (working copy)
95         @@ -9,6 +9,7 @@
96          # Optimisation.
97          use Memoize;
98          memoize("abs2rel");
99         +memoize("get_charset_from_locale");
100          
101          use vars qw{%config %links %oldlinks %oldpagemtime %pagectime
102                      %renderedfiles %pagesources %depends %hooks};
103         @@ -49,9 +50,15 @@
104                 adminemail => undef,
105                 plugin => [qw{mdwn inline htmlscrubber}],
106                 timeformat => '%c',
107         +       locale => get_preferred_locale(),
108          } #}}}
109             
110          sub checkconfig () { #{{{
111         +       debug("setting LC_ALL to '$config{locale}'");
112         +       eval q{use POSIX};
113         +       $ENV{LC_ALL} = $config{locale};
114         +       POSIX::setlocale(&POSIX::LC_ALL, $config{locale});
115         +
116                 if ($config{w3mmode}) {
117                         eval q{use Cwd q{abs_path}};
118                         $config{srcdir}=possibly_foolish_untaint(abs_path($config{srcdir}));
119         @@ -489,4 +496,50 @@
120                 $hooks{$param{type}}{$param{id}}=\%param;
121          } # }}}
122          
123         +sub get_preferred_locale() {
124         +       if (my $env = $ENV{LC_ALL}) {
125         +               return $env;
126         +       }
127         +
128         +       my @avail=`locale -a`;
129         +       chomp @avail;
130         +
131         +       return "POSIX" unless @avail;
132         +
133         +       my @ret;
134         +       # prefer UTF-8 locales
135         +       @avail = map { my $l = $_; $l =~ s/\.utf8/\.UTF-8/; $l; } @avail;
136         +       @avail = @ret if @ret = grep(/\.UTF-8$/, @avail);
137         +
138         +       # prefer en_US or en_ locales
139         +       return $ret[0] if @ret = grep(/^en_US/, @avail);
140         +       return $ret[0] if @ret = grep(/^en_/, @avail);
141         +       return $ret[0] if @ret = grep(/^[^.@]+$/, @avail);
142         +
143         +       # fallback to the first locale found
144         +       return $avail[0];
145         +} # }}}
146         +
147         +sub get_charset_from_locale($) {
148         +       my $locale=shift;
149         +       my ($l, $c);
150         +
151         +       my $supportedlist = "/usr/share/i18n/SUPPORTED";
152         +       if (defined $locale and open(SUPPORTED, "< $supportedlist")) {
153         +               while (<SUPPORTED>) {
154         +                       chomp;
155         +                       ($l, $c) = split(/\s+/);
156         +                       last if ($l eq $locale);
157         +               }
158         +               close(SUPPORTED);
159         +
160         +               return $c if ($l eq $locale);
161         +       }
162         +       return "ISO-8859-1";
163         +} # }}}
164         +
165         +sub decode_locale($) {
166         +       return decode(get_charset_from_locale($config{locale}), shift);
167         +} # }}}
168         +
169          1