aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Somerville <matthew-github@dracos.co.uk>2017-08-10 12:34:16 +0100
committerMatthew Somerville <matthew-github@dracos.co.uk>2017-08-10 12:34:16 +0100
commiteda7a2d88e13a89e22843203cf4f749c8679b4d7 (patch)
tree682568bc6f46d262e5755268ac65238af4562b52
parentb862ad1300116978f98eae9a079a22d49fc4f85e (diff)
parentdf1494f5a9de03c80764adf6108cbf699f547459 (diff)
Merge branch '1244-corps-translatable-body-names'
-rw-r--r--CHANGELOG.md2
-rwxr-xr-xbin/fixmystreet.com/fixture2
-rwxr-xr-xbin/oxfordshire/send-rdi-emails2
-rwxr-xr-xbin/zurich/overdue-alert4
-rw-r--r--db/downgrade_0052---0051.sql5
-rwxr-xr-xdb/rerun_dbic_loader.pl11
-rw-r--r--db/schema.sql10
-rw-r--r--db/schema_0052-translation-table.sql13
-rw-r--r--perllib/FixMyStreet/App.pm3
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm187
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm10
-rw-r--r--perllib/FixMyStreet/App/Controller/My.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm13
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm15
-rw-r--r--perllib/FixMyStreet/App/Model/DB.pm4
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Zurich.pm4
-rw-r--r--perllib/FixMyStreet/DB.pm17
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm7
-rw-r--r--perllib/FixMyStreet/DB/Result/Contact.pm8
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm14
-rw-r--r--perllib/FixMyStreet/DB/Result/Translation.pm44
-rw-r--r--perllib/FixMyStreet/DB/Schema.pm28
-rw-r--r--perllib/FixMyStreet/Roles/Translatable.pm97
-rw-r--r--perllib/FixMyStreet/Script/Alerts.pm4
-rwxr-xr-xperllib/FixMyStreet/Script/UpdateAllReports.pm2
-rw-r--r--perllib/FixMyStreet/Test.pm2
-rw-r--r--perllib/Open311/GetServiceRequestUpdates.pm2
-rw-r--r--perllib/Open311/PopulateServiceList.pm2
-rw-r--r--t/app/controller/admin.t2
-rw-r--r--t/app/controller/admin_translations.t191
-rw-r--r--t/app/controller/reports.t19
-rw-r--r--t/app/model/extra.t2
-rw-r--r--t/app/model/photoset.t2
-rw-r--r--t/app/model/problem.t9
-rw-r--r--t/app/sendreport/open311.t2
-rw-r--r--t/roles/translatable.t71
-rw-r--r--templates/web/base/admin/_translations.html19
-rw-r--r--templates/web/base/admin/body-form.html2
-rw-r--r--templates/web/base/admin/body.html7
-rw-r--r--templates/web/base/admin/category-checkboxes.html2
-rw-r--r--templates/web/base/admin/category-multiselect.html2
-rw-r--r--templates/web/base/admin/contact-form.html4
-rw-r--r--templates/web/base/admin/council_contacts.txt2
-rw-r--r--templates/web/base/admin/defecttypes/list.html2
-rw-r--r--templates/web/base/admin/problem_row.html2
-rw-r--r--templates/web/base/admin/report-category.html8
-rw-r--r--templates/web/base/admin/responsepriorities/list.html2
-rw-r--r--templates/web/base/dashboard/index.html2
-rw-r--r--templates/web/base/report/_inspect.html25
-rw-r--r--templates/web/base/report/_item.html2
-rw-r--r--templates/web/base/report/new/category.html6
-rw-r--r--templates/web/base/reports/_list-filters.html4
-rw-r--r--templates/web/zurich/admin/body.html2
-rw-r--r--templates/web/zurich/admin/contact-form.html2
-rw-r--r--templates/web/zurich/admin/problem_row.html2
-rw-r--r--templates/web/zurich/admin/report_edit-sdm.html2
-rw-r--r--templates/web/zurich/admin/report_edit.html6
-rw-r--r--templates/web/zurich/report/_main.html2
59 files changed, 785 insertions, 136 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb2bb5673..0f8e64bb8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
## Releases
* Unreleased
+ - New features:
+ - Body and category names can now be translated in the admin. #1244
- Front end improvements:
- Always show pagination figures even if only one page.
- Admin improvements:
diff --git a/bin/fixmystreet.com/fixture b/bin/fixmystreet.com/fixture
index d3b0a8349..912d34aab 100755
--- a/bin/fixmystreet.com/fixture
+++ b/bin/fixmystreet.com/fixture
@@ -31,7 +31,7 @@ my ($opt, $usage) = describe_options(
);
print($usage->text), exit if $opt->help;
-my $db = FixMyStreet::DB->storage;
+my $db = FixMyStreet::DB->schema->storage;
$db->txn_begin;
END {
if ($db) {
diff --git a/bin/oxfordshire/send-rdi-emails b/bin/oxfordshire/send-rdi-emails
index 11e7a6fe9..9eff02715 100755
--- a/bin/oxfordshire/send-rdi-emails
+++ b/bin/oxfordshire/send-rdi-emails
@@ -55,7 +55,7 @@ foreach my $inspector (@inspectors) {
};
my $result = FixMyStreet::Email::send_cron(
- FixMyStreet::DB->storage->schema,
+ FixMyStreet::DB->schema,
"rdi.txt", $params, $hdrs,
undef, 0, $cobrand,
);
diff --git a/bin/zurich/overdue-alert b/bin/zurich/overdue-alert
index 90e31df1c..f4fd0f4b7 100755
--- a/bin/zurich/overdue-alert
+++ b/bin/zurich/overdue-alert
@@ -38,7 +38,7 @@ loop_through( 'alert-overdue.txt', 0, 6, ['confirmed', 'planned'] );
sub loop_through {
my ( $template, $include_parent, $days, $states ) = @_;
- my $dtf = FixMyStreet::DB->storage->datetime_parser;
+ my $dtf = FixMyStreet::DB->schema->storage->datetime_parser;
my $date_threshold = $dtf->format_datetime(FixMyStreet::Cobrand::Zurich::sub_days( $now, $days ));
my $reports = FixMyStreet::DB->resultset("Problem")->search( {
@@ -78,7 +78,7 @@ sub send_alert {
}
FixMyStreet::Email::send_cron(
- FixMyStreet::DB->storage->schema,
+ FixMyStreet::DB->schema,
$template,
$h,
{
diff --git a/db/downgrade_0052---0051.sql b/db/downgrade_0052---0051.sql
new file mode 100644
index 000000000..715b4549f
--- /dev/null
+++ b/db/downgrade_0052---0051.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+DROP TABLE translation;
+
+COMMIT;
diff --git a/db/rerun_dbic_loader.pl b/db/rerun_dbic_loader.pl
index 958b28241..cf6e89ab2 100755
--- a/db/rerun_dbic_loader.pl
+++ b/db/rerun_dbic_loader.pl
@@ -3,6 +3,13 @@
use strict;
use warnings;
+BEGIN {
+ use File::Basename qw(dirname);
+ use File::Spec;
+ my $d = dirname(File::Spec->rel2abs($0));
+ require "$d/../setenv.pl";
+}
+
# This script inspects the current state of the database and then amends the
# FixMyStreet::DB::Result::* files to suit. After running the changes should be
# inspected before the code is commited.
@@ -20,13 +27,15 @@ my @tables_to_ignore = (
my $exclude = '^(?:' . join( '|', @tables_to_ignore ) . ')$';
make_schema_at(
- 'FixMyStreet::DB',
+ 'FixMyStreet::DB::Schema',
{
debug => 0, # switch on to be chatty
dump_directory => './perllib', # edit files in place
exclude => qr{$exclude}, # ignore some tables
generate_pod => 0, # no need for pod
overwrite_modifications => 1, # don't worry that the md5 is wrong
+ result_namespace => '+FixMyStreet::DB::Result',
+ resultset_namespace => '+FixMyStreet::DB::ResultSet',
# add in some extra components
components => [ 'FilterColumn', 'InflateColumn::DateTime', 'EncodedColumn' ],
diff --git a/db/schema.sql b/db/schema.sql
index d35071c0f..af6570b7a 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -528,3 +528,13 @@ CREATE TABLE contact_defect_types (
ALTER TABLE problem
ADD COLUMN defect_type_id int REFERENCES defect_types(id);
+
+CREATE TABLE translation (
+ id serial not null primary key,
+ tbl text not null,
+ object_id integer not null,
+ col text not null,
+ lang text not null,
+ msgstr text not null,
+ unique(tbl, object_id, col, lang)
+);
diff --git a/db/schema_0052-translation-table.sql b/db/schema_0052-translation-table.sql
new file mode 100644
index 000000000..95df499cc
--- /dev/null
+++ b/db/schema_0052-translation-table.sql
@@ -0,0 +1,13 @@
+BEGIN;
+
+CREATE TABLE translation (
+ id serial not null primary key,
+ tbl text not null,
+ object_id integer not null,
+ col text not null,
+ lang text not null,
+ msgstr text not null,
+ unique(tbl, object_id, col, lang)
+);
+
+COMMIT;
diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm
index c1628d010..2365118ea 100644
--- a/perllib/FixMyStreet/App.pm
+++ b/perllib/FixMyStreet/App.pm
@@ -168,6 +168,9 @@ template paths, maps, languages etc, etc.
sub setup_request {
my $c = shift;
+ # Set the Catalyst model schema to the same as the DB schema
+ $c->model("DB")->schema( FixMyStreet::DB->schema );
+
$c->setup_dev_overrides();
my $cobrand = $c->cobrand;
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index d1ac5e76b..0fa58eb9d 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -244,6 +244,9 @@ sub bodies : Path('bodies') : Args(0) {
$c->stash->{edit_activity} = $edit_activity;
+ $c->forward( 'fetch_languages' );
+ $c->forward( 'fetch_translations' );
+
my $posted = $c->get_param('posted') || '';
if ( $posted eq 'body' ) {
$c->forward('check_for_super_user');
@@ -257,6 +260,9 @@ sub bodies : Path('bodies') : Args(0) {
$c->model('DB::BodyArea')->create( { body => $body, area_id => $_ } );
}
+ $c->stash->{object} = $body;
+ $c->stash->{translation_col} = 'name';
+ $c->forward('update_translations');
$c->stash->{updated} = _('New body added');
}
}
@@ -300,30 +306,6 @@ sub body_form_dropdowns : Private {
$c->stash->{send_methods} = \@methods;
}
-sub body : Path('body') : Args(1) {
- my ( $self, $c, $body_id ) = @_;
-
- $c->stash->{body_id} = $body_id;
-
- unless ($c->user->has_permission_to('category_edit', $body_id)) {
- $c->forward('check_for_super_user');
- }
-
- $c->forward( '/auth/get_csrf_token' );
- $c->forward( 'lookup_body' );
- $c->forward( 'fetch_all_bodies' );
- $c->forward( 'body_form_dropdowns' );
-
- if ( $c->get_param('posted') ) {
- $c->log->debug( 'posted' );
- $c->forward('update_contacts');
- }
-
- $c->forward('fetch_contacts');
-
- return 1;
-}
-
sub check_for_super_user : Private {
my ( $self, $c ) = @_;
@@ -407,6 +389,12 @@ sub update_contacts : Private {
$contact->insert;
}
+ unless ( %errors ) {
+ $c->stash->{translation_col} = 'category';
+ $c->stash->{object} = $contact;
+ $c->forward('update_translations');
+ }
+
} elsif ( $posted eq 'update' ) {
$c->forward('/auth/check_csrf_token');
@@ -446,11 +434,45 @@ sub update_contacts : Private {
# Remove any others
$c->stash->{body}->body_areas->search( { area_id => [ keys %current ] } )->delete;
+ $c->stash->{translation_col} = 'name';
+ $c->stash->{object} = $c->stash->{body};
+ $c->forward('update_translations');
+
$c->stash->{updated} = _('Values updated');
}
}
}
+sub update_translations : Private {
+ my ( $self, $c ) = @_;
+
+ foreach my $lang (keys(%{$c->stash->{languages}})) {
+ my $id = $c->get_param('translation_id_' . $lang);
+ my $text = $c->get_param('translation_' . $lang);
+ if ($id) {
+ my $translation = $c->model('DB::Translation')->find(
+ {
+ id => $id,
+ }
+ );
+
+ if ($text) {
+ $translation->msgstr($text);
+ $translation->update;
+ } else {
+ $translation->delete;
+ }
+ } elsif ($text) {
+ my $col = $c->stash->{translation_col};
+ $c->stash->{object}->add_translation_for(
+ $col, $lang, $text
+ );
+ }
+ }
+
+ $c->stash->{updated} = _('Translations updated');
+}
+
sub body_params : Private {
my ( $self, $c ) = @_;
@@ -497,6 +519,44 @@ sub fetch_contacts : Private {
return 1;
}
+sub fetch_languages : Private {
+ my ( $self, $c ) = @_;
+
+ my $lang_map = {};
+ foreach my $lang (sort @{$c->cobrand->languages}) {
+ my ($id, $name, $code) = split(',', $lang);
+ $lang_map->{$id} = { name => $name, code => $code };
+ }
+
+ $c->stash->{languages} = $lang_map;
+
+ return 1;
+}
+
+sub fetch_translations : Private {
+ my ( $self, $c ) = @_;
+
+ my $translations = {};
+ if ($c->get_param('posted')) {
+ foreach my $lang (keys %{$c->stash->{languages}}) {
+ if (my $msgstr = $c->get_param('translation_' . $lang)) {
+ $translations->{$lang} = { msgstr => $msgstr };
+ }
+ if (my $id = $c->get_param('translation_id_' . $lang)) {
+ $translations->{$lang}->{id} = $id;
+ }
+ }
+ } elsif ($c->stash->{object}) {
+ my @translations = $c->stash->{object}->translation_for($c->stash->{translation_col})->all;
+
+ foreach my $tx (@translations) {
+ $translations->{$tx->lang} = { id => $tx->id, msgstr => $tx->msgstr };
+ }
+ }
+
+ $c->stash->{translations} = $translations;
+}
+
sub lookup_body : Private {
my ( $self, $c ) = @_;
@@ -516,35 +576,94 @@ sub lookup_body : Private {
return 1;
}
+sub body_base : Chained('/') : PathPart('admin/body') : CaptureArgs(0) { }
+
# This is for if the category name contains a '/'
-sub category_edit_all : Path('body') {
+sub category_edit_all : Chained('body_base') : PathPart('') {
my ( $self, $c, $body_id, @category ) = @_;
my $category = join( '/', @category );
- $c->go( 'category_edit', [ $body_id, $category ] );
-}
-sub category_edit : Path('body') : Args(2) {
- my ( $self, $c, $body_id, $category ) = @_;
+ $c->stash->{body_id} = $body_id;
+ $c->forward( 'lookup_body' );
+ my $contact = $c->stash->{body}->contacts->search( { category => $category } )->first;
+ $c->stash->{contact} = $contact;
+
+ $c->stash->{template} = 'admin/category_edit.html';
+ $c->forward( 'category_edit' );
+}
+
+sub body : Chained('body_base') : PathPart('') : CaptureArgs(1) {
+ my ( $self, $c, $body_id ) = @_;
$c->stash->{body_id} = $body_id;
+}
+
+sub edit_body : Chained('body') : PathPart('') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ unless ($c->user->has_permission_to('category_edit', $c->stash->{body_id})) {
+ $c->forward('check_for_super_user');
+ }
+
+ $c->forward( '/auth/get_csrf_token' );
+ $c->forward( 'lookup_body' );
+ $c->forward( 'fetch_all_bodies' );
+ $c->forward( 'body_form_dropdowns' );
+ $c->forward('fetch_languages');
+
+ if ( $c->get_param('posted') ) {
+ $c->forward('update_contacts');
+ }
+
+ $c->stash->{object} = $c->stash->{body};
+ $c->stash->{translation_col} = 'name';
+
+ # if there's a contact then it's because we're displaying error
+ # messages about adding a contact so grabbing translations will
+ # fetch the contact submitted translations. So grab them, stash
+ # them and then clear posted so we can fetch the body translations
+ if ($c->stash->{contact}) {
+ $c->forward('fetch_translations');
+ $c->stash->{contact_translations} = $c->stash->{translations};
+ }
+ $c->set_param('posted', '');
+
+ $c->forward('fetch_translations');
+ $c->forward('fetch_contacts');
+
+ $c->stash->{template} = 'admin/body.html';
+ return 1;
+}
+
+sub category : Chained('body') : PathPart('') : CaptureArgs(1) {
+ my ( $self, $c, $category ) = @_;
$c->forward( '/auth/get_csrf_token' );
$c->forward( 'lookup_body' );
my $contact = $c->stash->{body}->contacts->search( { category => $category } )->first;
$c->stash->{contact} = $contact;
+}
+
+sub category_edit : Chained('category') : PathPart('') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{translation_col} = 'category';
+ $c->stash->{object} = $c->stash->{contact};
+
+ $c->forward('fetch_languages');
+ $c->forward('fetch_translations');
my $history = $c->model('DB::ContactsHistory')->search(
{
- body_id => $body_id,
- category => $category
+ body_id => $c->stash->{body_id},
+ category => $c->stash->{contact}->category
},
{
order_by => ['contacts_history_id']
},
);
$c->stash->{history} = $history;
-
my @methods = map { $_ =~ s/FixMyStreet::SendReport:://; $_ } keys %{ FixMyStreet::SendReport->get_senders };
$c->stash->{send_methods} = \@methods;
@@ -925,8 +1044,8 @@ sub categories_for_point : Private {
# Remove the "Pick a category" option
shift @{$c->stash->{category_options}} if @{$c->stash->{category_options}};
- $c->stash->{categories} = $c->stash->{category_options};
- $c->stash->{categories_hash} = { map { $_ => 1 } @{$c->stash->{category_options}} };
+ $c->stash->{category_options_copy} = $c->stash->{category_options};
+ $c->stash->{categories_hash} = { map { $_->{name} => 1 } @{$c->stash->{category_options}} };
}
sub templates : Path('templates') : Args(0) {
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index bd9e80dc7..a8782eba2 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -196,8 +196,8 @@ sub display_location : Private {
my @pins;
unless ($c->get_param('no_pins')) {
@pins = map {
- # Here we might have a DB::Problem or a DB::Nearby, we always want the problem.
- my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_;
+ # Here we might have a DB::Problem or a DB::Result::Nearby, we always want the problem.
+ my $p = (ref $_ eq 'FixMyStreet::DB::Result::Nearby') ? $_->problem : $_;
$p->pin_data($c, 'around');
} @$on_map_all, @$nearby;
}
@@ -259,7 +259,7 @@ sub check_and_stash_category : Private {
distinct => 1
}
)->all;
- my @categories = map { $_->category } @contacts;
+ my @categories = map { { name => $_->category, value => $_->category_display } } @contacts;
$c->stash->{filter_categories} = \@categories;
my %categories_mapped = map { $_ => 1 } @categories;
@@ -311,8 +311,8 @@ sub ajax : Path('/ajax') {
# create a list of all the pins
my @pins = map {
- # Here we might have a DB::Problem or a DB::Nearby, we always want the problem.
- my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_;
+ # Here we might have a DB::Problem or a DB::Result::Nearby, we always want the problem.
+ my $p = (ref $_ eq 'FixMyStreet::DB::Result::Nearby') ? $_->problem : $_;
my $colour = $c->cobrand->pin_colour( $p, 'around' );
my $title = $c->cobrand->call_hook(pin_hover_title => $p, $p->title_safe) || $p->title_safe;
[ $p->latitude, $p->longitude,
diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm
index 6fee25ec5..ea7aee016 100644
--- a/perllib/FixMyStreet/App/Controller/My.pm
+++ b/perllib/FixMyStreet/App/Controller/My.pm
@@ -162,7 +162,7 @@ sub setup_page_data : Private {
distinct => 1,
order_by => [ 'category' ],
} )->all;
- @categories = map { $_->category } @categories;
+ @categories = map { { name => $_->category, value => $_->category_display } } @categories;
$c->stash->{filter_categories} = \@categories;
$c->stash->{page} = 'my';
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 3acb385bd..ab4c616fb 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -201,7 +201,7 @@ sub report_form_ajax : Path('ajax') : Args(0) {
if ($c->user_exists) {
my @bodies = keys %{$c->stash->{bodies}};
my $ca_another_user = $c->user->has_permission_to('contribute_as_another_user', \@bodies);
- my $ca_body = $c->user->has_permission_to('contribute_as_body', \@bodies);
+ my $ca_body = $c->user->from_body && $c->user->has_permission_to('contribute_as_body', \@bodies);
$contribute_as->{another_user} = $ca_another_user if $ca_another_user;
$contribute_as->{body} = $ca_body if $ca_body;
}
@@ -213,7 +213,6 @@ sub report_form_ajax : Path('ajax') : Args(0) {
category => $category,
extra_name_info => $extra_name_info,
titles_list => $extra_titles_list,
- categories => $c->stash->{category_options},
%$contribute_as ? (contribute_as => $contribute_as) : (),
$top_message ? (top_message => $top_message) : (),
}
@@ -645,7 +644,7 @@ sub setup_categories_and_bodies : Private {
$bodies_to_list{ $contact->body_id } = $contact->body;
unless ( $seen{$contact->category} ) {
- push @category_options, $contact->category;
+ push @category_options, { name => $contact->category, value => $contact->category_display };
my $metas = $contact->get_metadata_for_input;
$category_extras{$contact->category} = $metas if @$metas;
@@ -657,13 +656,15 @@ sub setup_categories_and_bodies : Private {
$non_public_categories{ $contact->category } = 1 if $contact->non_public;
}
- $seen{$contact->category} = 1;
+ $seen{$contact->category} = $contact->category_display;
}
if (@category_options) {
# If there's an Other category present, put it at the bottom
- @category_options = ( _('-- Pick a category --'), grep { $_ ne _('Other') } @category_options );
- push @category_options, _('Other') if $seen{_('Other')};
+ @category_options = (
+ { name => _('-- Pick a category --'), value => _('-- Pick a category --') },
+ grep { $_->{name} ne _('Other') } @category_options );
+ push @category_options, { name => _('Other'), value => $seen{_('Other')} } if $seen{_('Other')};
}
$c->cobrand->call_hook(munge_category_list => \@category_options, \@contacts, \%category_extras);
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index 8f068f0ec..33a1ac5b9 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -150,7 +150,7 @@ sub ward : Path : Args(2) {
distinct => 1,
order_by => [ 'category' ],
} )->all;
- @categories = map { $_->category } @categories;
+ @categories = map { { name => $_->category, value => $_->category_display } } @categories;
$c->stash->{filter_categories} = \@categories;
$c->stash->{filter_category} = { map { $_ => 1 } $c->get_param_list('filter_category', 1) };
@@ -318,6 +318,19 @@ sub body_check : Private {
}
}
+ my @translations = $c->model('DB::Translation')->search( {
+ tbl => 'body',
+ col => 'name',
+ msgstr => $q_body
+ } )->all;
+
+ if (@translations == 1) {
+ if ( my $body = $c->model('DB::Body')->find( { id => $translations[0]->object_id } ) ) {
+ $c->stash->{body} = $body;
+ return;
+ }
+ }
+
# No result, bad body name.
$c->detach( 'redirect_index' );
}
diff --git a/perllib/FixMyStreet/App/Model/DB.pm b/perllib/FixMyStreet/App/Model/DB.pm
index ac1f98dc9..9d09186b8 100644
--- a/perllib/FixMyStreet/App/Model/DB.pm
+++ b/perllib/FixMyStreet/App/Model/DB.pm
@@ -7,8 +7,8 @@ use warnings;
use FixMyStreet;
__PACKAGE__->config(
- schema_class => 'FixMyStreet::DB',
- connect_info => sub { FixMyStreet::DB->storage->dbh },
+ schema_class => 'FixMyStreet::DB::Schema',
+ connect_info => sub { FixMyStreet::DB->schema->storage->dbh },
);
=head1 NAME
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 4a886204c..1a0bbb0c8 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -269,6 +269,8 @@ sub set_lang_and_domain {
DateTime->DefaultLocale( 'en_US' );
}
+ FixMyStreet::DB->schema->lang($set_lang);
+
return $set_lang;
}
sub languages { FixMyStreet->config('LANGUAGES') || [] }
diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm
index 776350b25..c56f595ca 100644
--- a/perllib/FixMyStreet/Cobrand/Zurich.pm
+++ b/perllib/FixMyStreet/Cobrand/Zurich.pm
@@ -519,7 +519,7 @@ sub admin_report_edit {
# Can change category to any other
my @categories = $c->model('DB::Contact')->not_deleted->all;
- $c->stash->{categories} = [ map { $_->category } @categories ];
+ $c->stash->{category_options} = [ map { { name => $_->category, value => $_->category } } @categories ];
} elsif ($type eq 'dm') {
@@ -534,7 +534,7 @@ sub admin_report_edit {
# Can change category to any other
my @categories = $c->model('DB::Contact')->not_deleted->all;
- $c->stash->{categories} = [ map { $_->category } @categories ];
+ $c->stash->{category_options} = [ map { { name => $_->category, value => $_->category } } @categories ];
}
diff --git a/perllib/FixMyStreet/DB.pm b/perllib/FixMyStreet/DB.pm
index d920c809f..cee66b434 100644
--- a/perllib/FixMyStreet/DB.pm
+++ b/perllib/FixMyStreet/DB.pm
@@ -1,22 +1,13 @@
-use utf8;
package FixMyStreet::DB;
-# Created by DBIx::Class::Schema::Loader
-# DO NOT MODIFY THE FIRST PART OF THIS FILE
-
use strict;
use warnings;
+use FixMyStreet::DB::Schema;
-use base 'DBIx::Class::Schema';
-
-__PACKAGE__->load_namespaces;
-
-
-# Created by DBIx::Class::Schema::Loader v0.07017 @ 2012-03-08 17:19:55
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CjFpUvon7KggFM7OF7VK/w
+my $schema;
-use FixMyStreet;
+sub schema { $schema ||= FixMyStreet::DB::Schema->clone }
-__PACKAGE__->connection(FixMyStreet->dbic_connect_info);
+sub resultset { shift->schema->resultset(@_) }
1;
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 9a64d1608..db7777053 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -121,12 +121,19 @@ __PACKAGE__->has_many(
# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BOJANVwg3kR/1VjDq0LykA
+use Moo;
+use namespace::clean;
+
+with 'FixMyStreet::Roles::Translatable';
+
sub url {
my ( $self, $c, $args ) = @_;
# XXX $areas_info was used here for Norway parent - needs body parents, I guess
return $c->uri_for( '/reports/' . $c->cobrand->short_name( $self ), $args || {} );
}
+around name => \&translate_around;
+
sub areas {
my $self = shift;
my %ids = map { $_->area_id => 1 } $self->body_areas->all;
diff --git a/perllib/FixMyStreet/DB/Result/Contact.pm b/perllib/FixMyStreet/DB/Result/Contact.pm
index 3454c5806..fb731b9a3 100644
--- a/perllib/FixMyStreet/DB/Result/Contact.pm
+++ b/perllib/FixMyStreet/DB/Result/Contact.pm
@@ -82,12 +82,18 @@ __PACKAGE__->rabx_column('extra');
use Moo;
use namespace::clean -except => [ 'meta' ];
-with 'FixMyStreet::Roles::Extra';
+with 'FixMyStreet::Roles::Extra',
+ 'FixMyStreet::Roles::Translatable';
__PACKAGE__->many_to_many( response_templates => 'contact_response_templates', 'response_template' );
__PACKAGE__->many_to_many( response_priorities => 'contact_response_priorities', 'response_priority' );
__PACKAGE__->many_to_many( defect_types => 'contact_defect_types', 'defect_type' );
+sub category_display {
+ my $self = shift;
+ $self->translate('category');
+}
+
sub get_metadata_for_input {
my $self = shift;
my $id_field = $self->id_field;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index f353c02ee..afa117e4c 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -206,6 +206,7 @@ my $IM = eval {
with 'FixMyStreet::Roles::Abuser',
'FixMyStreet::Roles::Extra',
+ 'FixMyStreet::Roles::Translatable',
'FixMyStreet::Roles::PhotoSet';
=head2
@@ -456,12 +457,6 @@ sub check_for_errors {
$errors{category} = _('Please choose a category');
$self->category(undef);
}
- elsif ($self->category
- && $self->category eq _('-- Pick a property type --') )
- {
- $errors{category} = _('Please choose a property type');
- $self->category(undef);
- }
return \%errors;
}
@@ -489,6 +484,11 @@ sub confirm {
return 1;
}
+sub category_display {
+ my $self = shift;
+ $self->translate('category');
+}
+
sub bodies_str_ids {
my $self = shift;
return [] unless $self->bodies_str;
@@ -635,7 +635,7 @@ sub meta_line {
my $date_time = Utils::prettify_dt( $problem->confirmed );
my $meta = '';
- my $category = $problem->category;
+ my $category = $problem->category_display;
$category = $c->cobrand->call_hook(change_category_text => $category) || $category;
if ( $problem->anonymous ) {
diff --git a/perllib/FixMyStreet/DB/Result/Translation.pm b/perllib/FixMyStreet/DB/Result/Translation.pm
new file mode 100644
index 000000000..fafc7ccf1
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Result/Translation.pm
@@ -0,0 +1,44 @@
+use utf8;
+package FixMyStreet::DB::Result::Translation;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
+__PACKAGE__->table("translation");
+__PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_nullable => 0,
+ sequence => "translation_id_seq",
+ },
+ "tbl",
+ { data_type => "text", is_nullable => 0 },
+ "object_id",
+ { data_type => "integer", is_nullable => 0 },
+ "col",
+ { data_type => "text", is_nullable => 0 },
+ "lang",
+ { data_type => "text", is_nullable => 0 },
+ "msgstr",
+ { data_type => "text", is_nullable => 0 },
+);
+__PACKAGE__->set_primary_key("id");
+__PACKAGE__->add_unique_constraint(
+ "translation_tbl_object_id_col_lang_key",
+ ["tbl", "object_id", "col", "lang"],
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-07-14 23:24:32
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:///VNqg4BOuO29xKhnY8vw
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
diff --git a/perllib/FixMyStreet/DB/Schema.pm b/perllib/FixMyStreet/DB/Schema.pm
new file mode 100644
index 000000000..45d731c33
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Schema.pm
@@ -0,0 +1,28 @@
+use utf8;
+package FixMyStreet::DB::Schema;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Schema';
+
+__PACKAGE__->load_namespaces(
+ result_namespace => "+FixMyStreet::DB::Result",
+ resultset_namespace => "+FixMyStreet::DB::ResultSet",
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-07-13 14:15:09
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UpH30RXb6SbCqRv2FPmpkg
+
+use Moo;
+use FixMyStreet;
+
+__PACKAGE__->connection(FixMyStreet->dbic_connect_info);
+
+has lang => ( is => 'rw' );
+
+1;
diff --git a/perllib/FixMyStreet/Roles/Translatable.pm b/perllib/FixMyStreet/Roles/Translatable.pm
new file mode 100644
index 000000000..49f10d51a
--- /dev/null
+++ b/perllib/FixMyStreet/Roles/Translatable.pm
@@ -0,0 +1,97 @@
+package FixMyStreet::Roles::Translatable;
+
+use Moo::Role;
+
+sub translate_around {
+ my ($orig, $self) = (shift, shift);
+ my $fallback = $self->$orig(@_);
+ (my $col = (caller(2))[3]) =~ s/.*:://;
+ $self->_translate($col, $fallback);
+}
+
+sub translate {
+ my ($self, $col) = (shift, shift);
+ my $fallback = $self->$col(@_);
+ $self->_translate($col, $fallback);
+}
+
+sub _translate {
+ my ($self, $col, $fallback) = @_;
+
+ my $schema = $self->result_source->schema;
+ my $table = lc $self->result_source->source_name;
+ my $id = $self->id;
+
+ # Deal with the fact problem table has denormalized copy of category string
+ if ($table eq 'problem' && $col eq 'category') {
+ my $body_id = $self->bodies_str_ids->[0];
+ return $fallback unless $body_id && $body_id =~ /^[0-9]+$/;
+ my $contact = $schema->resultset("Contact")->find( {
+ body_id => $body_id,
+ category => $fallback,
+ } );
+ return $fallback unless $contact; # Shouldn't happen, but some tests
+ $table = 'contact';
+ $id = $contact->id;
+ }
+
+ if (ref $schema) {
+ my $translation = $schema->resultset('Translation')->find({
+ lang => $schema->lang,
+ tbl => $table,
+ object_id => $id,
+ col => $col
+ });
+ return $translation->msgstr if $translation;
+ } else {
+ warn "Can't use translation on this call to $table.$col";
+ }
+ return $fallback;
+};
+
+# These next two functions (translation_for and and_translation_for) are
+# convenience methods for use in the translation interface in the admin.
+# They shouldn't be used else where as they don't take account of things
+# like denormalised strings (e.g report category)
+sub translation_for {
+ my ($self, $col, $lang) = @_;
+
+ my $schema = $self->result_source->schema;
+
+ my $props = {
+ tbl => lc $self->result_source->source_name,
+ object_id => $self->id,
+ col => $col
+ };
+
+ if ($lang) {
+ $props->{lang} = $lang;
+ }
+
+ my $translations = $schema->resultset('Translation')->search($props);
+
+ return $lang ? $translations->first : $translations;
+}
+
+sub add_translation_for {
+ my ($self, $col, $lang, $msgstr) = @_;
+
+ my $schema = $self->result_source->schema;
+
+ my $props = {
+ tbl => lc $self->result_source->source_name,
+ object_id => $self->id,
+ col => $col,
+ lang => $lang,
+ msgstr => $msgstr,
+ };
+
+ my $translation = $schema->resultset('Translation')->update_or_create(
+ $props,
+ { key => 'translation_tbl_object_id_col_lang_key' }
+ );
+
+ return $translation;
+}
+
+1;
diff --git a/perllib/FixMyStreet/Script/Alerts.pm b/perllib/FixMyStreet/Script/Alerts.pm
index ef1bdb08b..aefe13318 100644
--- a/perllib/FixMyStreet/Script/Alerts.pm
+++ b/perllib/FixMyStreet/Script/Alerts.pm
@@ -62,7 +62,7 @@ sub send() {
$query =~ s/\?/alert.parameter/ if ($query =~ /\?/);
$query =~ s/\?/alert.parameter2/ if ($query =~ /\?/);
- $query = FixMyStreet::DB->storage->dbh->prepare($query);
+ $query = FixMyStreet::DB->schema->storage->dbh->prepare($query);
$query->execute();
my $last_alert_id;
my %data = ( template => $alert_type->template, data => [], schema => $schema );
@@ -225,7 +225,7 @@ sub send() {
and (select whenqueued from alert_sent where alert_sent.alert_id = ? and alert_sent.parameter::integer = problem.id) is null
and users.email <> ?
order by confirmed desc";
- $q = FixMyStreet::DB->storage->dbh->prepare($q);
+ $q = FixMyStreet::DB->schema->storage->dbh->prepare($q);
$q->execute($latitude, $longitude, $d, $alert->whensubscribed, $alert->id, $alert->user->email);
while (my $row = $q->fetchrow_hashref) {
$schema->resultset('AlertSent')->create( {
diff --git a/perllib/FixMyStreet/Script/UpdateAllReports.pm b/perllib/FixMyStreet/Script/UpdateAllReports.pm
index 51cb7b856..1bd069ee8 100755
--- a/perllib/FixMyStreet/Script/UpdateAllReports.pm
+++ b/perllib/FixMyStreet/Script/UpdateAllReports.pm
@@ -158,7 +158,7 @@ sub generate_dashboard {
);
$data{last_seven_days} = \%last_seven_days;
- my $dtf = FixMyStreet::DB->storage->datetime_parser;
+ my $dtf = FixMyStreet::DB->schema->storage->datetime_parser;
my $eight_ago = $dtf->format_datetime(DateTime->now->subtract(days => 8));
%problems_reported_by_period = stuff_by_day_or_year('day',
'Problem',
diff --git a/perllib/FixMyStreet/Test.pm b/perllib/FixMyStreet/Test.pm
index add67dfd9..6b6bc02bc 100644
--- a/perllib/FixMyStreet/Test.pm
+++ b/perllib/FixMyStreet/Test.pm
@@ -8,7 +8,7 @@ use utf8;
use Test::More;
use FixMyStreet::DB;
-my $db = FixMyStreet::DB->storage;
+my $db = FixMyStreet::DB->schema->storage;
sub import {
strict->import;
diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm
index 30db24164..6ba53b7af 100644
--- a/perllib/Open311/GetServiceRequestUpdates.pm
+++ b/perllib/Open311/GetServiceRequestUpdates.pm
@@ -11,7 +11,7 @@ has start_date => ( is => 'ro', default => sub { undef } );
has end_date => ( is => 'ro', default => sub { undef } );
has suppress_alerts => ( is => 'rw', default => 0 );
has verbose => ( is => 'ro', default => 0 );
-has schema => ( is =>'ro', lazy => 1, default => sub { FixMyStreet::DB->connect } );
+has schema => ( is =>'ro', lazy => 1, default => sub { FixMyStreet::DB->schema->connect } );
Readonly::Scalar my $AREA_ID_BROMLEY => 2482;
Readonly::Scalar my $AREA_ID_OXFORDSHIRE => 2237;
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index 540425bf1..764207626 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -6,7 +6,7 @@ use Open311;
has bodies => ( is => 'ro' );
has found_contacts => ( is => 'rw', default => sub { [] } );
has verbose => ( is => 'ro', default => 0 );
-has schema => ( is => 'ro', lazy => 1, default => sub { FixMyStreet::DB->connect } );
+has schema => ( is => 'ro', lazy => 1, default => sub { FixMyStreet::DB->schema->connect } );
has _current_body => ( is => 'rw' );
has _current_open311 => ( is => 'rw' );
diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t
index db7f9fc3c..069e00b5e 100644
--- a/t/app/controller/admin.t
+++ b/t/app/controller/admin.t
@@ -182,7 +182,7 @@ subtest 'check contact creation' => sub {
non_public => 'on',
} } );
$mech->get_ok('/admin/body/' . $body->id . '/test/category');
-
+ $mech->content_contains('<h1>test/category</h1>');
};
subtest 'check contact editing' => sub {
diff --git a/t/app/controller/admin_translations.t b/t/app/controller/admin_translations.t
new file mode 100644
index 000000000..f5c32baa6
--- /dev/null
+++ b/t/app/controller/admin_translations.t
@@ -0,0 +1,191 @@
+use FixMyStreet::TestMech;
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1);
+
+$mech->log_in_ok( $superuser->email );
+
+FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ MAPIT_TYPES => [ 'UTA' ],
+}, sub {
+
+my $body = $mech->create_body_ok(2650, 'Aberdeen City Council');
+$mech->create_contact_ok( body_id => $body->id, category => 'Traffic lights', email => 'lights@example.com' );
+
+subtest 'check no translations if one language' => sub {
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_lacks( 'Translations' );
+
+};
+
+};
+
+FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ MAPIT_TYPES => [ 'UTA' ],
+ LANGUAGES => [
+ 'en-gb,English,en_GB',
+ 'de,German,de_DE'
+ ]
+}, sub {
+
+my $body = $mech->create_body_ok(2650, 'Aberdeen City Council');
+$mech->create_contact_ok( body_id => $body->id, category => 'Traffic lights', email => 'lights@example.com' );
+
+my $body2 = $mech->create_body_ok(2643, 'Arun District Council');
+
+FixMyStreet::DB->resultset("Translation")->create({
+ lang => "de",
+ tbl => "body",
+ object_id => $body2->id,
+ col => "name",
+ msgstr => "DE Arun",
+});
+
+subtest 'check translations if multiple languages' => sub {
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_contains( 'Translations' );
+};
+
+subtest 'check add category with translation' => sub {
+ $mech->get_ok('/admin/body/' . $body2->id);
+
+ $mech->content_contains('DE Arun');
+
+ $mech->submit_form_ok( { with_fields => {
+ category => 'Potholes',
+ translation_de => 'DE potholes',
+ email => 'potholes@example.org',
+ } } );
+
+ # check that error page includes translations
+ $mech->content_lacks('DE Arun');
+ $mech->content_contains('DE potholes');
+
+ $mech->submit_form_ok( { with_fields => {
+ category => 'Potholes',
+ translation_de => 'DE potholes',
+ email => 'potholes@example.org',
+ note => 'adding category with translation',
+ } } );
+
+ $mech->content_contains('DE Arun');
+ $mech->content_lacks('DE potholes');
+
+ $mech->get_ok('/admin/body/' . $body2->id . '/Potholes');
+
+ $mech->content_contains( 'DE potholes' );
+};
+
+subtest 'check add category translation' => sub {
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_lacks( 'DE Traffic lights' );
+
+ $mech->submit_form_ok( { with_fields => {
+ translation_de => 'DE Traffic lights',
+ note => 'updating translation',
+ } } );
+
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_contains( 'DE Traffic lights' );
+};
+
+subtest 'check replace category translation' => sub {
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_contains( 'DE Traffic lights' );
+
+ $mech->submit_form_ok( { with_fields => {
+ translation_de => 'German Traffic lights',
+ note => 'updating translation',
+ } } );
+
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_lacks( 'DE Traffic lights' );
+ $mech->content_contains( 'German Traffic lights' );
+};
+
+subtest 'delete category translation' => sub {
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+ $mech->content_contains( 'German Traffic lights' );
+
+ $mech->submit_form_ok( { with_fields => {
+ translation_de => '',
+ note => 'updating translation',
+ } } );
+
+ $mech->get_ok('/admin/body/' . $body->id . '/Traffic%20lights');
+
+ $mech->content_lacks( 'DE German Traffic lights' );
+};
+
+subtest 'check add body translation' => sub {
+ $mech->get_ok('/admin/body/' . $body->id);
+
+ $mech->content_lacks( 'DE Aberdeen' );
+
+ $mech->submit_form_ok( { with_fields => {
+ send_method => 'email',
+ translation_de => 'DE Aberdeen',
+ } } );
+
+ $mech->content_contains( 'DE Aberdeen' );
+};
+
+subtest 'check replace body translation' => sub {
+ $mech->get_ok('/admin/body/' . $body->id);
+
+ $mech->content_contains( 'DE Aberdeen' );
+
+ $mech->submit_form_ok( { with_fields => {
+ send_method => 'email',
+ translation_de => 'German Aberdeen',
+ } } );
+
+ $mech->content_lacks( 'DE Aberdeen' );
+ $mech->content_contains( 'German Aberdeen' );
+};
+
+subtest 'delete body translation' => sub {
+ $mech->get_ok('/admin/body/' . $body->id);
+ $mech->content_contains( 'German Aberdeen' );
+
+ $mech->submit_form_ok( { with_fields => {
+ send_method => 'email',
+ translation_de => '',
+ } } );
+
+ $mech->content_lacks( 'DE German Aberdeen' );
+};
+
+subtest 'check add body with translation' => sub {
+ $mech->get_ok('/admin/bodies/');
+ $mech->submit_form_ok( { with_fields => {
+ area_ids => 2643,
+ send_method => 'email',
+ translation_de => 'DE A Body',
+ } } );
+
+ # check that error page includes translations
+ $mech->content_contains( 'DE A Body' );
+
+ $mech->submit_form_ok( { with_fields => {
+ name => 'A body',
+ area_ids => 2643,
+ send_method => 'email',
+ translation_de => 'DE A Body',
+ } } );
+
+ $mech->follow_link_ok({ text => 'A body' });
+ $mech->content_contains( 'DE A Body' );
+}
+};
+
+done_testing();
diff --git a/t/app/controller/reports.t b/t/app/controller/reports.t
index dd84d3d2e..9f28a6c89 100644
--- a/t/app/controller/reports.t
+++ b/t/app/controller/reports.t
@@ -25,6 +25,16 @@ my @edinburgh_problems = $mech->create_problems_for_body(3, $body_edin_id, 'All
my @westminster_problems = $mech->create_problems_for_body(5, $body_west_id, 'All reports', { category => 'Graffiti' });
my @fife_problems = $mech->create_problems_for_body(15, $body_fife_id, 'All reports', { category => 'Flytipping' });
+my $west_trans = FixMyStreet::DB->resultset('Translation')->find_or_create({
+ tbl => 'body',
+ object_id => $body_west_id,
+ col => 'name',
+ lang => 'de',
+ msgstr => 'De Westminster'
+});
+
+ok $west_trans, 'created westminster translation';
+
is scalar @westminster_problems, 5, 'correct number of westminster problems created';
is scalar @edinburgh_problems, 3, 'correct number of edinburgh problems created';
is scalar @fife_problems, 15, 'correct number of fife problems created';
@@ -267,4 +277,13 @@ subtest "it lists shortlisted reports" => sub {
};
};
+subtest "can use translated body name" => sub {
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->get_ok('/reports/De Westminster');
+ $mech->title_like(qr/Westminster City Council/);
+ };
+};
+
done_testing();
diff --git a/t/app/model/extra.t b/t/app/model/extra.t
index 4e2a8ed37..17f34c6c1 100644
--- a/t/app/model/extra.t
+++ b/t/app/model/extra.t
@@ -2,7 +2,7 @@ use FixMyStreet::Test;
use DateTime;
-my $db = FixMyStreet::DB->storage->schema;
+my $db = FixMyStreet::DB->schema;
my $body = $db->resultset('Body')->create({ name => 'ExtraTestingBody' });
diff --git a/t/app/model/photoset.t b/t/app/model/photoset.t
index 3651a188a..4aa5c8992 100644
--- a/t/app/model/photoset.t
+++ b/t/app/model/photoset.t
@@ -10,7 +10,7 @@ my $dt = DateTime->now;
my $UPLOAD_DIR = tempdir( CLEANUP => 1 );
-my $db = FixMyStreet::DB->storage->schema;
+my $db = FixMyStreet::DB->schema;
my $user = $db->resultset('User')->find_or_create({
name => 'Bob', email => 'bob@example.com',
diff --git a/t/app/model/problem.t b/t/app/model/problem.t
index 76b7c476a..718b980b0 100644
--- a/t/app/model/problem.t
+++ b/t/app/model/problem.t
@@ -117,15 +117,6 @@ for my $test (
}
},
{
- desc => 'bad category',
- changed => {
- category => '-- Pick a property type --',
- },
- errors => {
- category => 'Please choose a property type',
- }
- },
- {
desc => 'correct category',
changed => {
category => 'Horse!',
diff --git a/t/app/sendreport/open311.t b/t/app/sendreport/open311.t
index 8f933065c..32564dbd8 100644
--- a/t/app/sendreport/open311.t
+++ b/t/app/sendreport/open311.t
@@ -157,7 +157,7 @@ sub test_overrides {
FixMyStreet::override_config {
ALLOWED_COBRANDS => ['fixmystreet', 'oxfordshire', 'bromley', 'westberkshire', 'greenwich'],
}, sub {
- my $db = FixMyStreet::DB->storage->schema;
+ my $db = FixMyStreet::DB->schema;
#$db->txn_begin;
my $params = { id => $input->{body_id}, name => $input->{body_name} };
diff --git a/t/roles/translatable.t b/t/roles/translatable.t
new file mode 100644
index 000000000..71e39c360
--- /dev/null
+++ b/t/roles/translatable.t
@@ -0,0 +1,71 @@
+use FixMyStreet::TestMech;
+my $mech = FixMyStreet::TestMech->new;
+
+my $body = FixMyStreet::DB->resultset("Body")->create({ name => 'Dunkirk' });
+my $contact = $mech->create_contact_ok(
+ body => $body,
+ email => 'potholes@dunkirk',
+ category => 'Potholes'
+);
+
+FixMyStreet::DB->resultset("Translation")->create({
+ lang => "fr",
+ tbl => "body",
+ object_id => $body->id,
+ col => "name",
+ msgstr => "Dunkerque",
+});
+
+FixMyStreet::DB->resultset("Translation")->create({
+ lang => "de",
+ tbl => "contact",
+ object_id => $contact->id,
+ col => "category",
+ msgstr => "Schlaglöcher",
+});
+
+FixMyStreet::DB->resultset("Translation")->create({
+ lang => "nb",
+ tbl => "contact",
+ object_id => $contact->id,
+ col => "category",
+ msgstr => "Hull i veien",
+});
+
+my ($problem) = $mech->create_problems_for_body(1, $body->id, "Title", {
+ whensent => \'current_timestamp',
+ category => 'Potholes',
+});
+
+is $body->name, "Dunkirk";
+is $contact->category_display, "Potholes";
+is $problem->category_display, "Potholes";
+
+FixMyStreet::DB->schema->lang("fr");
+is $body->name, "Dunkerque";
+is $contact->category_display, "Potholes";
+is $problem->category_display, "Potholes";
+
+FixMyStreet::DB->schema->lang("de");
+is $body->name, "Dunkirk";
+is $contact->category_display, "Schlaglöcher";
+is $problem->category_display, "Schlaglöcher";
+
+is $contact->translation_for('category', 'de')->msgstr, "Schlaglöcher";
+is $body->translation_for('name', 'fr')->msgstr, "Dunkerque";
+
+ok $body->add_translation_for('name', 'es', 'Dunkerque');
+
+FixMyStreet::DB->schema->lang("es");
+is $body->name, "Dunkerque";
+
+is $body->translation_for('name')->count, 2;
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'fiksgatami' ],
+}, sub {
+ $mech->get_ok($problem->url);
+ $mech->content_contains('Hull i veien');
+};
+
+done_testing;
diff --git a/templates/web/base/admin/_translations.html b/templates/web/base/admin/_translations.html
new file mode 100644
index 000000000..d2e0ba322
--- /dev/null
+++ b/templates/web/base/admin/_translations.html
@@ -0,0 +1,19 @@
+[% IF languages.size > 1 %]
+<h2>[% loc('Translations') %]</h2>
+ <input type="hidden" name="token" value="[% csrf_token %]" >
+<table>
+ <tr>
+ <th>[% loc('Language') %]</th>
+ <th>[% loc('Translation') %]</th>
+ </tr>
+ [% FOREACH language IN languages.keys %]
+ <tr>
+ <td>
+ <label for="translation_[% language %]">[% languages.$language.name %] ([% language %])</label>
+ <input type="hidden" name="translation_id_[% language %]" value="[% translations.$language.id %]">
+ </td>
+ <td><input type="text" name="translation_[% language %]" id="translation_[% language %]" value="[% translations.$language.msgstr %]"></td>
+ </tr>
+ [% END %]
+</table>
+[% END %]
diff --git a/templates/web/base/admin/body-form.html b/templates/web/base/admin/body-form.html
index 6c750bcaf..55d0e500c 100644
--- a/templates/web/base/admin/body-form.html
+++ b/templates/web/base/admin/body-form.html
@@ -25,6 +25,8 @@
<input type="text" class="form-control" name="name" id="name" value="[% body.name | html %]" size="50">
</p>
+ [% INCLUDE 'admin/_translations.html' %]
+
<div class="admin-hint">
<p>
[% loc(
diff --git a/templates/web/base/admin/body.html b/templates/web/base/admin/body.html
index 82032c0c3..7fa446f44 100644
--- a/templates/web/base/admin/body.html
+++ b/templates/web/base/admin/body.html
@@ -79,7 +79,7 @@
</tr>
[% WHILE ( cat = contacts.next ) %]
<tr [% IF cat.state == 'deleted' %]class="is-deleted"[% END %]>
- <td class="contact-category"><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category | html %]</a>
+ <td class="contact-category"><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category_display | html %]</a>
<br>[% cat.email | html %]</td>
<td>
[% cat.state %]
@@ -130,9 +130,10 @@
<div class="fms-admin-warning">
[% errors.values.join('<br>') %]
</div>
+ [% INCLUDE 'admin/contact-form.html' translations=contact_translations %]
+ [% ELSE %]
+ [% INCLUDE 'admin/contact-form.html' translations={} %]
[% END %]
-
- [% INCLUDE 'admin/contact-form.html' %]
</div>
[% IF NOT errors and c.user.is_superuser %]
diff --git a/templates/web/base/admin/category-checkboxes.html b/templates/web/base/admin/category-checkboxes.html
index 63acd4112..9d5ab17ea 100644
--- a/templates/web/base/admin/category-checkboxes.html
+++ b/templates/web/base/admin/category-checkboxes.html
@@ -11,7 +11,7 @@
<li>
<label>
<input type="checkbox" name="contacts[[% contact.id %]]" [% 'checked' IF contact.active %]/>
- [% contact.category %]
+ [% contact.category_display %]
</label>
</li>
[% END %]
diff --git a/templates/web/base/admin/category-multiselect.html b/templates/web/base/admin/category-multiselect.html
index 98416204f..4e4eceeed 100644
--- a/templates/web/base/admin/category-multiselect.html
+++ b/templates/web/base/admin/category-multiselect.html
@@ -4,7 +4,7 @@
<p>
<select class="form-control js-multiple" name="categories" id="categories" multiple data-all="[% loc('All categories') %]">
[% FOR contact IN contacts %]
- <option value="[% contact.id %]" [% 'selected' IF contact.active %]>[% contact.category | html %]</option>
+ <option value="[% contact.id %]" [% 'selected' IF contact.active %]>[% contact.category_display | html %]</option>
[% END %]
</select>
</p>
diff --git a/templates/web/base/admin/contact-form.html b/templates/web/base/admin/contact-form.html
index 0694459cb..375b3eb99 100644
--- a/templates/web/base/admin/contact-form.html
+++ b/templates/web/base/admin/contact-form.html
@@ -2,7 +2,7 @@
[% IF contact.in_storage %]
<p>
- <h1>[% contact.category | html %]</h2>
+ <h1>[% contact.category_display | html %]</h1>
<input type="hidden" name="category" value="[% contact.category | html %]" >
</p>
[% ELSE %]
@@ -20,6 +20,8 @@
</p>
[% END %]
+ [% INCLUDE 'admin/_translations.html' %]
+
<div class="admin-hint">
<p>
[% loc("The <strong>email address</strong> is the destination to which reports about this category will be sent.
diff --git a/templates/web/base/admin/council_contacts.txt b/templates/web/base/admin/council_contacts.txt
index ffab83ab6..f11615789 100644
--- a/templates/web/base/admin/council_contacts.txt
+++ b/templates/web/base/admin/council_contacts.txt
@@ -1,4 +1,4 @@
[% WHILE ( contact = contacts.next ) -%]
[%- NEXT IF contact.state != 'confirmed' %]
-[% contact.category %] [% contact.email %]
+[% contact.category_display %] [% contact.email %]
[%- END %]
diff --git a/templates/web/base/admin/defecttypes/list.html b/templates/web/base/admin/defecttypes/list.html
index 783bd085c..ffff89eff 100644
--- a/templates/web/base/admin/defecttypes/list.html
+++ b/templates/web/base/admin/defecttypes/list.html
@@ -20,7 +20,7 @@
<em>[% ('All categories') %]</em>
[% ELSE %]
[% FOR contact IN d.contacts %]
- [% contact.category %][% ',' UNLESS loop.last %]
+ [% contact.category_display %][% ',' UNLESS loop.last %]
[% END %]
[% END %]
</td>
diff --git a/templates/web/base/admin/problem_row.html b/templates/web/base/admin/problem_row.html
index e7e18dee9..def6ce60f 100644
--- a/templates/web/base/admin/problem_row.html
+++ b/templates/web/base/admin/problem_row.html
@@ -20,7 +20,7 @@
<br>[% loc('Anonymous') %]: [% IF problem.anonymous %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]
</td>
<td>
- [% PROCESS value_or_nbsp value=problem.category %]
+ [% PROCESS value_or_nbsp value=problem.category_display %]
<br>[%- IF edit_body_contacts -%]
[% FOR body IN problem.bodies.values %]
<a href="[% c.uri_for('body', body.id ) %]">[% PROCESS value_or_nbsp value=body.name %]</a>
diff --git a/templates/web/base/admin/report-category.html b/templates/web/base/admin/report-category.html
index 1e39236d7..a2290089b 100644
--- a/templates/web/base/admin/report-category.html
+++ b/templates/web/base/admin/report-category.html
@@ -1,13 +1,13 @@
<select class="form-control" name="category" id="category">
[% IF NOT problem.category OR NOT categories_hash.${problem.category} %]
<optgroup label="[% loc('Existing category') %]">
- <option selected value="[% problem.category | html %]">[% (problem.category OR '-') | html %]</option>
+ <option selected value="[% problem.category | html %]">[% (problem.category_display OR '-') | html %]</option>
</optgroup>
[% END %]
- [% IF categories.size %]
+ [% IF category_options_copy.size %]
<optgroup label="[% loc('Available categories') %]">
- [% FOREACH cat IN categories %]
- <option[% ' selected' IF problem.category == cat %]>[% cat | html %]</option>
+ [% FOREACH cat IN category_options_copy %]
+ <option value="[% cat.name | html %]"[% ' selected' IF problem.category == cat.name %]>[% cat.value | html %]</option>
[% END %]
</optgroup>
[% END %]
diff --git a/templates/web/base/admin/responsepriorities/list.html b/templates/web/base/admin/responsepriorities/list.html
index 4c05ca14d..80d4e2cee 100644
--- a/templates/web/base/admin/responsepriorities/list.html
+++ b/templates/web/base/admin/responsepriorities/list.html
@@ -19,7 +19,7 @@
<em>[% loc('All categories') %]</em>
[% ELSE %]
[% FOR contact IN p.contacts %]
- [% contact.category %][% ',' UNLESS loop.last %]
+ [% contact.category_display %][% ',' UNLESS loop.last %]
[% END %]
[% END %]
</td>
diff --git a/templates/web/base/dashboard/index.html b/templates/web/base/dashboard/index.html
index 2a9a2ef42..6033ef36b 100644
--- a/templates/web/base/dashboard/index.html
+++ b/templates/web/base/dashboard/index.html
@@ -29,7 +29,7 @@
<label for="category">[% loc('Category:') %]</label>
<select class="form-control" name="category"><option value=''>[% loc('All') %]</option>
[% FOR cat_op IN category_options %]
- <option value='[% cat_op | html %]'[% ' selected' IF category == cat_op %]>[% cat_op | html %]</option>
+ <option value='[% cat_op.name | html %]'[% ' selected' IF category == cat_op.name %]>[% cat_op.value | html %]</option>
[% END %]
</select>
</p>
diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html
index bf57cb858..922cc480b 100644
--- a/templates/web/base/report/_inspect.html
+++ b/templates/web/base/report/_inspect.html
@@ -50,18 +50,21 @@
[% INCLUDE 'admin/report-category.html' %]
</p>
- [% FOREACH category IN categories %]
- [% cat_prefix = category | lower | replace('[^a-z]', '') %]
- [% cat_prefix = "category_" _ cat_prefix _ "_" %]
- [% IF category == problem.category %]
- <p data-category="[% category | html %]" data-priorities='[% priorities_by_category.$category %]' data-defect-types='[% category_defect_types.$category %]' data-templates='[% templates_by_category.$category %]'>
- [% INCLUDE 'report/new/category_extras_fields.html' %]
- </p>
- [% ELSE %]
- <p data-category="[% category | html %]" class="hidden" data-priorities='[% priorities_by_category.$category %]' data-defect-types='[% category_defect_types.$category %]' data-templates='[% templates_by_category.$category %]'>
- [% INCLUDE 'report/new/category_extras_fields.html' report_meta='' %]
+ [% FOREACH category IN category_options_copy %]
+ [% cat_name = category.name;
+ cat_prefix = cat_name | lower | replace('[^a-z]', '');
+ cat_prefix = "category_" _ cat_prefix _ "_" %]
+ <p data-category="[% cat_name | html %]"
+ [%~ IF cat_name != problem.category %] class="hidden"[% END %]
+ data-priorities='[% priorities_by_category.$cat_name %]'
+ data-defect-types='[% category_defect_types.$cat_name %]'
+ ata-templates='[% templates_by_category.$cat_name %]'>
+ [% IF cat_name == problem.category %]
+ [% INCLUDE 'report/new/category_extras_fields.html' %]
+ [% ELSE %]
+ [% INCLUDE 'report/new/category_extras_fields.html' report_meta='' %]
+ [% END %]
</p>
- [% END %]
[% END %]
[% IF permissions.report_inspect %]
diff --git a/templates/web/base/report/_item.html b/templates/web/base/report/_item.html
index 7a3530b1f..a892086ca 100644
--- a/templates/web/base/report/_item.html
+++ b/templates/web/base/report/_item.html
@@ -90,7 +90,7 @@
</div>
<div>
<h4>[% loc('Category') %]</h4>
- <p>[% (problem.category OR '-') | html %]</p>
+ <p>[% (problem.category_display OR '-') | html %]</p>
</div>
<div>
<h4>[% loc('State') %]</h4>
diff --git a/templates/web/base/report/new/category.html b/templates/web/base/report/new/category.html
index 4c66f00fb..b8ebf1653 100644
--- a/templates/web/base/report/new/category.html
+++ b/templates/web/base/report/new/category.html
@@ -7,9 +7,9 @@
</label>[% =%]
<select class="form-control" name='category' id='form_category' data-role='[% c.user.has_body_permission_to('planned_reports') ? 'inspector' : 'user' %]' data-body='[% c.user.from_body.name %]'>
[%~ FOREACH cat_op IN category_options ~%]
- [% cat_op_lc = cat_op | lower =%]
- <option value='[% cat_op | html %]'[% ' selected' IF report.category == cat_op || category_lc == cat_op_lc || (category_options.size == 2 AND loop.last) ~%]
- >[% IF loop.first %][% cat_op %][% ELSE %][% cat_op | html %][% END %]</option>
+ [% cat_op_lc = cat_op.name | lower =%]
+ <option value='[% cat_op.name | html %]'[% ' selected' IF report.category == cat_op.name || category_lc == cat_op_lc || (category_options.size == 2 AND loop.last) ~%]
+ >[% IF loop.first %][% cat_op.value %][% ELSE %][% cat_op.value | html %][% END %]</option>
[%~ END =%]
</select>
[%~ END ~%]
diff --git a/templates/web/base/reports/_list-filters.html b/templates/web/base/reports/_list-filters.html
index efb917c16..73afe64f4 100644
--- a/templates/web/base/reports/_list-filters.html
+++ b/templates/web/base/reports/_list-filters.html
@@ -19,8 +19,8 @@
[% IF filter_categories.size %]
<select class="form-control js-multiple" name="filter_category" id="filter_categories" multiple data-all="[% loc('Everything') %]">
[% FOR cat IN filter_categories %]
- <option value="[% cat | html %]"[% ' selected' IF filter_category.$cat %]>
- [% cat | html %]
+ <option value="[% cat.name | html %]"[% ' selected' IF filter_category.${cat.name} %]>
+ [% cat.value | html %]
</option>
[% END %]
</select>
diff --git a/templates/web/zurich/admin/body.html b/templates/web/zurich/admin/body.html
index cf860990d..468f7adff 100644
--- a/templates/web/zurich/admin/body.html
+++ b/templates/web/zurich/admin/body.html
@@ -19,7 +19,7 @@
</tr>
[% WHILE ( cat = contacts.next ) %]
<tr[% IF cat.deleted %] class="is-deleted"[% END %]>
- <td><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category %]</a></td>
+ <td><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category_display %]</a></td>
<td>[% cat.email | html %]</td>
<td>[% cat.editor %]</td>
<td>[% cat.note | html %]</td>
diff --git a/templates/web/zurich/admin/contact-form.html b/templates/web/zurich/admin/contact-form.html
index aaf7a1797..236b169d0 100644
--- a/templates/web/zurich/admin/contact-form.html
+++ b/templates/web/zurich/admin/contact-form.html
@@ -1,5 +1,5 @@
<form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" id="category_edit">
- <p><strong>[% loc('Category:') %] </strong>[% contact.category | html %]
+ <p><strong>[% loc('Category:') %] </strong>[% contact.category_display | html %]
<input type="hidden" name="category" value="[% contact.category | html %]" >
<input type="hidden" name="token" value="[% csrf_token %]" >
diff --git a/templates/web/zurich/admin/problem_row.html b/templates/web/zurich/admin/problem_row.html
index acbf17017..a83e22b27 100644
--- a/templates/web/zurich/admin/problem_row.html
+++ b/templates/web/zurich/admin/problem_row.html
@@ -15,7 +15,7 @@
[% END %]
</td>
<td>[% PROCESS value_or_nbsp value=problem.title %]</td>
- <td>[% PROCESS value_or_nbsp value=problem.category %]</td>
+ <td>[% PROCESS value_or_nbsp value=problem.category_display %]</td>
<td>[% PROCESS format_date this_date=problem.created %]</td>
<td>[% PROCESS format_date this_date=problem.lastupdate %]</td>
<td> [% states.${problem.state} %][% IF problem.state == 'planned';
diff --git a/templates/web/zurich/admin/report_edit-sdm.html b/templates/web/zurich/admin/report_edit-sdm.html
index 3df9459f9..07f0332d5 100644
--- a/templates/web/zurich/admin/report_edit-sdm.html
+++ b/templates/web/zurich/admin/report_edit-sdm.html
@@ -70,7 +70,7 @@
</dd>
<dt>[% loc('Category:') %] <!-- Kategorie --></dt>
- <dd>[% problem.category | html %]</dd>
+ <dd>[% problem.category_display | html %]</dd>
<dt class="print-only">[% loc('State:') %] <!-- Status --></dt>
<dd class="print-only">[% states.${problem.state} %]</dd>
diff --git a/templates/web/zurich/admin/report_edit.html b/templates/web/zurich/admin/report_edit.html
index f87dcb5cf..35075a9f0 100644
--- a/templates/web/zurich/admin/report_edit.html
+++ b/templates/web/zurich/admin/report_edit.html
@@ -98,7 +98,7 @@
</dd>
<dt>[% loc('Category:') %] <!-- Kategorie --></dt>
- <dd>[% problem.category | html %]</dd>
+ <dd>[% problem.category_display | html %]</dd>
<dt class="print-only">[% loc('State:') %] <!-- Status --></dt>
<dd class="print-only">[% states_trans.${problem.state} %]</dd>
@@ -169,8 +169,8 @@
<label for="category">[% loc('Assign to different category:') %]</label>
<select class="form-control" name="category" id="category">
<option value="">--</option>
- [% FOREACH cat IN categories %]
- <option value="[% cat %]">[% cat %]</option>
+ [% FOREACH cat IN category_options %]
+ <option value="[% cat.name %]">[% cat.value %]</option>
[% END %]
</select>
</div>
diff --git a/templates/web/zurich/report/_main.html b/templates/web/zurich/report/_main.html
index c1e4d15e6..b868c03e2 100644
--- a/templates/web/zurich/report/_main.html
+++ b/templates/web/zurich/report/_main.html
@@ -1,5 +1,5 @@
<div class="problem-header clearfix">
- <h1>[% tprintf( loc('Reported in the %s category'), problem.category ) %]</h1>
+ <h1>[% tprintf( loc('Reported in the %s category'), problem.category_display ) %]</h1>
<p class="sub">
[% prettify_dt( problem.created, 'zurich' ) %]
[%- IF !problem.used_map %]<br>[% loc('there is no pin shown as the user did not use the map') %][% END %]