4 use YAML::Syck qw(Load LoadFile);
11 locale-diff - Compare two YAML files and print how their datastructures differ
15 # --keys is the default
17 diff --keys en.yml is.yml
19 # --untranslated-values compares prints keys whose values don't differ
20 diff --untranslated-values-all en.yml is.yml
22 # --untranslated-values-all compares prints keys whose values
23 # don't differ. Ignoring the blacklist which prunes things
24 # unlikley to be translated
25 diff --untranslated-values-all en.yml is.yml
29 This utility prints the differences between two YAML files using
30 L<Test::Differences>. The purpose of it is to diff the files is
31 F<config/locales> to find out what keys need to be added to the
32 translated files when F<en.yml> changes.
40 Print this help message.
44 Show the hash keys that differ between the two files, useful merging
45 new entries from F<en.yml> to a local file.
47 =item --untranslated-values
49 Show keys whose values are either exactly the same between the two
50 files, or don't exist in the target file (the latter file
51 specified). The values are pruned according to global and language
52 specific blacklists found in the C<__DATA__> section of this script.
54 This helps to find untranslated values.
56 =item --untranslated-values-all
58 Like C<--untranslated-values> but ignores blacklists.
64 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@f-prot.com>
68 # Get the command-line options
69 Getopt::Long::Parser->new(
70 config => [ qw< bundling no_ignore_case no_require_order pass_through > ],
72 'h|help' => \my $help,
74 'untranslated-values' => \my $untranslated_values,
75 'untranslated-values-all' => \my $untranslated_values_all,
78 # --keys is the default
79 $keys = 1 if not $untranslated_values_all and not $untranslated_values;
84 # If we're not given two .yml files
85 help() if @ARGV != 2 or (!-f $ARGV[0] or !-f $ARGV[1]);
87 my ($from, $to) = @ARGV;
89 my $from_data = LoadFile($from);
90 my $to_data = LoadFile($to);
92 my $from_parsed = { iterate($from_data->{basename($from)}) };
93 my $to_parsed = { iterate($to_data->{basename($to)}) };
95 # Since this used to be the default, support that...
98 print_key_differences();
100 elsif ($untranslated_values or $untranslated_values_all)
102 my @untranslated = untranslated_keys($from_parsed, $to_parsed);
104 # Prune according to blacklist
105 if ($untranslated_values) {
106 @untranslated = prune_untranslated_with_blacklist(basename($to), @untranslated);
109 print $_, "\n" for @untranslated;
114 sub print_key_differences
116 # Hack around Test::Differences wanting a Test::* module loaded
118 sub Test::ok { print shift }
121 eq_or_diff([ sort keys %$from_parsed ], [ sort keys %$to_parsed ]);
124 sub untranslated_keys
126 my ($from_parsed, $to_parsed) = @_;
127 sort grep { not exists $to_parsed->{$_} or $from_parsed->{$_} eq $to_parsed->{$_} } keys %$from_parsed;
130 sub prune_untranslated_with_blacklist
132 my ($language, @keys) = @_;
136 my $end_yaml = Load(join '', <DATA>);
137 my $untranslated_values = $end_yaml->{untranslated_values};
138 my $default = $untranslated_values->{default};
139 my $this_language = $untranslated_values->{$language} || {};
141 my %bw_list = (%$default, %$this_language);
143 while (my ($key, $blacklisted) = each %bw_list)
145 # FIXME: Does syck actually support true/false booleans in yaml?
146 delete $keys{$key} if $blacklisted eq 'true'
154 my ($hash, @path) = @_;
157 while (my ($k, $v) = each %$hash)
159 if (ref $v eq 'HASH')
161 push @ret => iterate($v, @path, $k);
165 push @ret => join(".",@path, $k), $v;
175 $name =~ s[\..*?$][];
183 Pod::Usage::pod2usage(
184 -verbose => $arg{ verbose },
185 -exitval => $arg{ exitval } || 0,
192 # Default/Per language blacklist/whitelist for the
193 # --untranslated-values switch. "true" as a value indicates that the
194 # key is to be blacklisted, and "false" that it's to be
195 # whitelisted. "false" is only required to whitelist a key
196 # blacklisted by default on a per-language basis.
200 layouts.intro_3_bytemark: true
201 layouts.intro_3_ucl: true
202 layouts.project_name.h1: true
203 layouts.project_name.title: true
204 site.index.license.project_url: true
206 activerecord.attributes.message.sender: true
207 activerecord.attributes.trace.name: true
208 activerecord.models.changeset: true
209 activerecord.models.relation: true
210 browse.changeset.changeset: true
211 browse.changeset.changesetxml: true
212 browse.changeset.osmchangexml: true
213 browse.changeset.title: true
214 browse.common_details.version: true
215 browse.containing_relation.relation: true
216 browse.relation.relation: true
217 browse.relation.relation_title: true
218 browse.start_rjs.details: true
219 browse.start_rjs.object_list.details: true
220 browse.tag_details.tags: true
221 changeset.changesets.id: true
222 export.start.export_button: true
223 export.start.format: true
224 export.start.output: true
225 export.start.zoom: true
226 export.start_rjs.export: true
229 layouts.shop_url: true
230 notifier.gpx_notification.failure.import_failures_url: true
231 notifier.signup_confirm_plain.wiki_signup_url: true
232 site.edit.anon_edits: true
233 site.edit.anon_edits_link: true
234 site.index.license.license_name: true
235 site.index.permalink: true
236 site.search.submit_text: true
237 trace.edit.tags: true
239 trace.trace_form.tags: true
240 trace.trace_optionals.tags: true
241 trace.view.tags: true
242 user.account.public editing.enabled link: true