From b6306105a8d025713b034914fe96b2c35f39026e Mon Sep 17 00:00:00 2001 From: John Barrett Date: Wed, 4 Jun 2014 11:31:55 +0100 Subject: [PATCH 1/9] Added high-lighting for placeholders in tokens in the translation interface --- lib/DDGC.pm | 5 +++++ root/static/css/translate.css | 6 +++++- templates/translate/tokens/token_language.tx | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/DDGC.pm b/lib/DDGC.pm index 5be4393a2..9644275ff 100644 --- a/lib/DDGC.pm +++ b/lib/DDGC.pm @@ -394,6 +394,11 @@ sub _build_xslate { mark_raw(substr($_[0],0,-2).''.substr($_[0],-2).'') }, + hilight_token_placeholders => sub { + $_[0] =~ s{(%\d?[s|d])}{$1}g; + return mark_raw($_[0]); + }, + # String::Truncate's elide function truncate => \&elide, diff --git a/root/static/css/translate.css b/root/static/css/translate.css index 1b42a1002..3691525bc 100644 --- a/root/static/css/translate.css +++ b/root/static/css/translate.css @@ -218,6 +218,10 @@ font-size: 16px; } } + +span.hilight-token { + color: #990000; +} @media only screen and (max-width: 40em) { .translate-overview .half.progress { width: 100%; clear: both; } @@ -235,4 +239,4 @@ } @media only screen and (max-width: 30em) { .translate-comments.content-box .row { padding-left: 8px; } -} \ No newline at end of file +} diff --git a/templates/translate/tokens/token_language.tx b/templates/translate/tokens/token_language.tx index 63a70ab8c..663c11f6f 100644 --- a/templates/translate/tokens/token_language.tx +++ b/templates/translate/tokens/token_language.tx @@ -8,14 +8,14 @@
- <: $token_language.token.msgid :> + <: hilight_token_placeholders($token_language.token.msgid) :> <: if !$token_language_page { :>Discuss<: } :>
<: if $token_language.token.msgid_plural { :>
- <: $token_language.token.msgid_plural :> + <: hilight_token_placeholders($token_language.token.msgid_plural) :>
<: } :> From 10b4d56b9796d40dd36e1176db059ebb552ebd41 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Wed, 4 Jun 2014 11:47:17 +0100 Subject: [PATCH 2/9] Fixed token hilighting regex, missing $ anchor for placeholder ordering. --- lib/DDGC.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DDGC.pm b/lib/DDGC.pm index 9644275ff..a02719eca 100644 --- a/lib/DDGC.pm +++ b/lib/DDGC.pm @@ -395,7 +395,7 @@ sub _build_xslate { }, hilight_token_placeholders => sub { - $_[0] =~ s{(%\d?[s|d])}{$1}g; + $_[0] =~ s{(%\d?\$?[s|d])}{$1}g; return mark_raw($_[0]); }, From ad92b31dcece8b1e7260913a6fb432f055d20cb2 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Thu, 19 Jun 2014 14:39:57 +0100 Subject: [PATCH 3/9] Moving screenshot handling out of the Forum controller. Should hopefully be reusable by other components. --- lib/DDGC/Web/Controller/Forum/My.pm | 53 +++++-------------------- lib/DDGC/Web/Controller/Screenshot.pm | 56 +++++++++++++++++++++++++++ templates/forum/my/thread_form.tx | 4 +- 3 files changed, 67 insertions(+), 46 deletions(-) create mode 100644 lib/DDGC/Web/Controller/Screenshot.pm diff --git a/lib/DDGC/Web/Controller/Forum/My.pm b/lib/DDGC/Web/Controller/Forum/My.pm index 670159458..16c6e44ec 100644 --- a/lib/DDGC/Web/Controller/Forum/My.pm +++ b/lib/DDGC/Web/Controller/Forum/My.pm @@ -60,51 +60,16 @@ sub thread_form { $c->session->{thread_forms} = {} unless defined $c->session->{thread_forms}; $c->session->{thread_forms}->{$c->stash->{thread_form_id}} = {} unless defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}; - if ($c->req->param('screenshot')) { - my $upload = $c->req->uploads->{screenshot}; - my $media = $c->d->rs('Media')->create_via_file($c->user->db, $upload->tempname,{ - upload_filename => $upload->filename, - content_type => $upload->type - }); - my $screenshot = $c->d->rs('Screenshot')->create({ - media_id => $media->id, - }); - $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} = [] - unless defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}; - push @{$c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}}, - $screenshot->id; - $c->stash->{x} = { - screenshot_id => $screenshot->id, - media_url => $media->url, - }; - $c->forward('View::JSON'); - } elsif ($c->req->param('delete_screenshot')) { - my $delete_id = $c->req->param('delete_screenshot'); - $c->stash->{x} = { - screenshot_id => $delete_id - }; - my $screenshot = $c->d->rs('Screenshot')->find($delete_id); - $screenshot->media->delete - if $screenshot->media->users_id == $c->user->id || $c->user->admin; - my @old_ids = @{$c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}}; - my @new_ids; - for (@old_ids) { - push @new_ids, $_ unless $_ == $delete_id; - } - $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} = [@new_ids]; - $c->forward('View::JSON'); - } else { - my @screenshot_ids; - if (defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}) { - @screenshot_ids = @{$c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}}; - } elsif (defined $c->stash->{thread}) { - @screenshot_ids = $c->stash->{thread}->sorted_screenshots->ids; - $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} = [@screenshot_ids]; - } - $c->stash->{screenshots} = $c->d->rs('Screenshot')->search({ - id => { -in => [@screenshot_ids] }, - }); + my @screenshot_ids; + if (defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}) { + @screenshot_ids = @{$c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}}; + } elsif (defined $c->stash->{thread}) { + @screenshot_ids = $c->stash->{thread}->sorted_screenshots->ids; + $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} = [@screenshot_ids]; } + $c->stash->{screenshots} = $c->d->rs('Screenshot')->search({ + id => { -in => [@screenshot_ids] }, + }); } sub thread : Chained('base') CaptureArgs(1) { diff --git a/lib/DDGC/Web/Controller/Screenshot.pm b/lib/DDGC/Web/Controller/Screenshot.pm new file mode 100644 index 000000000..192972cea --- /dev/null +++ b/lib/DDGC/Web/Controller/Screenshot.pm @@ -0,0 +1,56 @@ +package DDGC::Web::Controller::Screenshot; +# ABSTRACT: Screenshot management + +use Moose; +BEGIN { extends 'Catalyst::Controller'; } + +sub base : Chained('/base') PathPart('screenshot') CaptureArgs(0) { + my ( $self, $c ) = @_; + if (!$c->user) { + $c->response->redirect($c->chained_uri('My','login')); + return $c->detach; + } +} + +sub manage : Chained('base') Args(1) { + my ( $self, $c, $form_id ) = @_; + if ($c->req->param('screenshot')) { + my $upload = $c->req->uploads->{screenshot}; + my $media = $c->d->rs('Media')->create_via_file($c->user->db, $upload->tempname,{ + upload_filename => $upload->filename, + content_type => $upload->type + }); + my $screenshot = $c->d->rs('Screenshot')->create({ + media_id => $media->id, + }); + $c->session->{thread_forms}->{$form_id}->{screenshots} = [] unless defined $c->session->{thread_forms}->{$form_id}->{screenshots}; + push @{$c->session->{thread_forms}->{$form_id}->{screenshots}}, $screenshot->id; + $c->stash->{x} = { + screenshot_id => $screenshot->id, + media_url => $media->url, + }; + $c->forward('View::JSON'); + } + elsif ($c->req->param('delete_screenshot')) { + my $delete_id = $c->req->param('delete_screenshot'); + $c->stash->{x} = { + screenshot_id => $delete_id, + }; + my $screenshot = $c->d->rs('Screenshot')->find($delete_id); + if ($screenshot->media->users_id == $c->user->id || $c->user->admin) { + $screenshot->media->delete; + } + else { + $c->res->code(403); + $c->stash->{x} = { + error => 'Forbidden', + }; + return $c->forward('View::JSON'); + } + @{$c->session->{thread_forms}->{$form_id}->{screenshots}} = grep { $_ != $delete_id } @{$c->session->{thread_forms}->{$form_id}->{screenshots}}; + $c->forward('View::JSON'); + } +} + +no Moose; +__PACKAGE__->meta->make_immutable; diff --git a/templates/forum/my/thread_form.tx b/templates/forum/my/thread_form.tx index 73fc16c89..ac95f778f 100644 --- a/templates/forum/my/thread_form.tx +++ b/templates/forum/my/thread_form.tx @@ -63,7 +63,7 @@ $(function(){ new Dropzone("#screenshots", { - url: "<: if $thread.id { $u('Forum::My','edit',$thread.id, { thread_form_id => $thread_form_id }) } else { $u('Forum::My','newthread',$forum_index, { thread_form_id => $thread_form_id }) } :>", + url: "<: $u('Screenshot', 'manage', $thread_form_id ) :>", paramName: 'screenshot', maxFilesize: '2', addRemoveLinks: true, @@ -71,7 +71,7 @@ removedfile: function(file){ if (file.screenshot_id) { $.post( - "<: if $thread.id { $u('Forum::My','edit',$thread.id, { thread_form_id => $thread_form_id }) } else { $u('Forum::My','newthread',$forum_index, { thread_form_id => $thread_form_id }) } :>", + "<: $u('Screenshot', 'manage', $thread_form_id ) :>", { delete_screenshot: file.screenshot_id }, function(){ file.previewElement.remove(); From 4b41928be7051f64f51a5be1cd1147c1508c6c62 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Tue, 24 Jun 2014 11:11:21 +0100 Subject: [PATCH 4/9] A unique form identifier will be useful. It's currently only used for threads, but can be used to namespace screenshots and action_tokens across any and all forms. --- lib/DDGC/Web.pm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/DDGC/Web.pm b/lib/DDGC/Web.pm index 4f387373c..fdeef7737 100644 --- a/lib/DDGC/Web.pm +++ b/lib/DDGC/Web.pm @@ -27,6 +27,7 @@ extends 'Catalyst'; use DDGC::Config; use Class::Load ':all'; use Digest::MD5 qw( md5_hex ); +use Data::UUID; use DDGC::Web::Wizard::Unvoted; use DDGC::Web::Wizard::Untranslated; @@ -118,11 +119,7 @@ sub d { sub ddgc { shift->d(@_) } sub next_form_id { - my ( $c ) = @_; - my $last_id = $c->session->{last_form_id} || int(rand(1_000_000)); - my $next_id = $last_id + int(rand(1_000)); - $c->session->{last_form_id} = $next_id; - return $next_id; + Data::UUID->new->create_str; } sub set_new_action_token { From 7079779e870da718f544cf59c126e1cf56da073d Mon Sep 17 00:00:00 2001 From: John Barrett Date: Tue, 24 Jun 2014 11:55:41 +0100 Subject: [PATCH 5/9] Fixing screenshot / token result, was copied from screenshot / thread --- lib/DDGC/DB/Result/Screenshot/Token.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/DDGC/DB/Result/Screenshot/Token.pm b/lib/DDGC/DB/Result/Screenshot/Token.pm index adeaeeaf6..8815f61ca 100644 --- a/lib/DDGC/DB/Result/Screenshot/Token.pm +++ b/lib/DDGC/DB/Result/Screenshot/Token.pm @@ -1,5 +1,5 @@ package DDGC::DB::Result::Screenshot::Token; -# ABSTRACT: A screenshot on a thread +# ABSTRACT: A screenshot on a token use Moose; use MooseX::NonMoose; @@ -8,9 +8,9 @@ use DBIx::Class::Candy; use DateTime::Format::RSS; use namespace::autoclean; -table 'screenshot_thread'; +table 'screenshot_token'; -sub u { shift->thread->u(@_) } +sub u { shift->token->u(@_) } column id => { data_type => 'bigint', From bac277c23f54500039fe8e8996cade88e135e2a7 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Tue, 24 Jun 2014 11:56:47 +0100 Subject: [PATCH 6/9] Adding 'retired' column to token. --- lib/DDGC/DB/Result/Token.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/DDGC/DB/Result/Token.pm b/lib/DDGC/DB/Result/Token.pm index a203b7cef..333bcb435 100644 --- a/lib/DDGC/DB/Result/Token.pm +++ b/lib/DDGC/DB/Result/Token.pm @@ -81,6 +81,12 @@ column updated => { set_on_update => 1, }; +column retired => { + data_type => 'tinyint', + is_nullable => 0, + default_value => 0, +}; + belongs_to 'token_domain', 'DDGC::DB::Result::Token::Domain', 'token_domain_id'; has_many 'token_languages', 'DDGC::DB::Result::Token::Language', 'token_id'; From a5e292832a959b7e11a4e229e99a4ef07fa3e733 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Tue, 24 Jun 2014 12:04:36 +0100 Subject: [PATCH 7/9] Added a 'fuzzy' field to TokenLanguage Can be used to mark a translation in need of review if an automated merge of translations was carried out. --- lib/DDGC/DB/Result/Token/Language.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/DDGC/DB/Result/Token/Language.pm b/lib/DDGC/DB/Result/Token/Language.pm index 33c373746..2f9796cf9 100644 --- a/lib/DDGC/DB/Result/Token/Language.pm +++ b/lib/DDGC/DB/Result/Token/Language.pm @@ -90,6 +90,12 @@ column updated => { set_on_update => 1, }; +column fuzzy => { + data_type => 'tinyint', + is_nullable => 0, + default_value => 0, +}; + belongs_to 'token', 'DDGC::DB::Result::Token', 'token_id', { on_delete => 'cascade', }; From 549a3e7cdac4689de972c9b694498c08c675e418 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Tue, 24 Jun 2014 12:22:24 +0100 Subject: [PATCH 8/9] Breaking dropzone component out into its own include template. %s/thread_form_id/form_id/g since screenshots won't apply to just threads. --- lib/DDGC/Web/Controller/Forum/My.pm | 20 ++++++------ templates/forum/my/thread_form.tx | 48 ++--------------------------- templates/i/screenshot.tx | 48 +++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 56 deletions(-) create mode 100644 templates/i/screenshot.tx diff --git a/lib/DDGC/Web/Controller/Forum/My.pm b/lib/DDGC/Web/Controller/Forum/My.pm index 16c6e44ec..6eda41b6f 100644 --- a/lib/DDGC/Web/Controller/Forum/My.pm +++ b/lib/DDGC/Web/Controller/Forum/My.pm @@ -45,8 +45,8 @@ sub newthread : Chained('base') Args(1) { $c->req->params->{content}, forum => $c->stash->{forum_index}, title => $c->req->params->{title}, - defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} - ? ( screenshot_ids => $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} ) + defined $c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots} + ? ( screenshot_ids => $c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots} ) : (), ); $c->response->redirect($c->chained_uri(@{$thread->u})); @@ -56,16 +56,16 @@ sub newthread : Chained('base') Args(1) { sub thread_form { my ( $self, $c ) = @_; - $c->stash->{thread_form_id} = $c->req->param('thread_form_id') || $c->next_form_id; + $c->stash->{form_id} = $c->req->param('form_id') || $c->next_form_id; $c->session->{thread_forms} = {} unless defined $c->session->{thread_forms}; - $c->session->{thread_forms}->{$c->stash->{thread_form_id}} = {} - unless defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}; + $c->session->{thread_forms}->{$c->stash->{form_id}} = {} + unless defined $c->session->{thread_forms}->{$c->stash->{form_id}}; my @screenshot_ids; - if (defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}) { - @screenshot_ids = @{$c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}}; + if (defined $c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots}) { + @screenshot_ids = @{$c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots}}; } elsif (defined $c->stash->{thread}) { @screenshot_ids = $c->stash->{thread}->sorted_screenshots->ids; - $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots} = [@screenshot_ids]; + $c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots} = [@screenshot_ids]; } $c->stash->{screenshots} = $c->d->rs('Screenshot')->search({ id => { -in => [@screenshot_ids] }, @@ -108,8 +108,8 @@ sub edit : Chained('thread') Args(0) { $c->stash->{thread}->comment->content($c->req->params->{content}); $c->stash->{thread}->comment->update; my @screenshot_ids; - if (defined $c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}) { - @screenshot_ids = @{$c->session->{thread_forms}->{$c->stash->{thread_form_id}}->{screenshots}}; + if (defined $c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots}) { + @screenshot_ids = @{$c->session->{thread_forms}->{$c->stash->{form_id}}->{screenshots}}; } $c->stash->{thread}->screenshot_threads->search_rs({ screenshot_id => { -not_in => [@screenshot_ids] }, diff --git a/templates/forum/my/thread_form.tx b/templates/forum/my/thread_form.tx index ac95f778f..33e2ffe69 100644 --- a/templates/forum/my/thread_form.tx +++ b/templates/forum/my/thread_form.tx @@ -10,7 +10,7 @@ <: } :>
- +
@@ -60,50 +60,6 @@ } $('input[name="title"]').keypress(suggest); - $(function(){ - - new Dropzone("#screenshots", { - url: "<: $u('Screenshot', 'manage', $thread_form_id ) :>", - paramName: 'screenshot', - maxFilesize: '2', - addRemoveLinks: true, - acceptedFiles: 'image/*', - removedfile: function(file){ - if (file.screenshot_id) { - $.post( - "<: $u('Screenshot', 'manage', $thread_form_id ) :>", - { delete_screenshot: file.screenshot_id }, - function(){ - file.previewElement.remove(); - } - ).fail(function(){ - alert('Delete failed'); - }); - } else { - file.previewElement.remove(); - } - }, - init: function() { - - <: if $screenshots { :> - <: for results($screenshots) -> $screenshot { :> - var mockFile<: $screenshot.id :> = { - name: "<: $screenshot.upload_filename :>", - screenshot_id: <: $screenshot.id :>, - media_url: "<: $screenshot.media.url :>", - }; - this.options.addedfile.call(this, mockFile<: $screenshot.id :>); - this.options.thumbnail.call(this, mockFile<: $screenshot.id :>, '<: $screenshot.media.url_thumbnail :>'); - <: } :> - <: } :> - - this.on("success", function(file, response) { - file.screenshot_id = response.screenshot_id; - file.media_url = response.media_url; - }); - } - }); - - }); +<: i('screenshot') :> diff --git a/templates/i/screenshot.tx b/templates/i/screenshot.tx new file mode 100644 index 000000000..ae47ffa3f --- /dev/null +++ b/templates/i/screenshot.tx @@ -0,0 +1,48 @@ + + From 69d5ae11430be97dbcb62a9afafa849529da28f9 Mon Sep 17 00:00:00 2001 From: John Barrett Date: Tue, 24 Jun 2014 14:38:08 +0100 Subject: [PATCH 9/9] Do not count tokens with only invalid translations as done - fixes issue #155 --- lib/DDGC/DB/Result/Token/Domain/Language.pm | 11 ++++++++--- lib/DDGC/DB/ResultSet/Token/Language.pm | 13 +++++++++++-- lib/DDGC/Web/Controller/Translate.pm | 10 ++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/DDGC/DB/Result/Token/Domain/Language.pm b/lib/DDGC/DB/Result/Token/Domain/Language.pm index 69a7cb6d3..558f2c28e 100644 --- a/lib/DDGC/DB/Result/Token/Domain/Language.pm +++ b/lib/DDGC/DB/Result/Token/Domain/Language.pm @@ -255,9 +255,14 @@ sub search_tokens { sub untranslated_tokens { my ( $self, $page, $pagesize ) = @_; - $self->_get_token_languages(1, $page, $pagesize, { - 'token_language_translations.id' => undef, - },{ + $self->_get_token_languages(1, $page, $pagesize, + { 'token_language.id' => { + -not_in => $self->result_source->schema->resultset('Token::Language::Translation')->search({ + check_result => '1', + })->get_column('token_language_id')->as_query, + }, + }, + { join => 'token_language_translations', }); } diff --git a/lib/DDGC/DB/ResultSet/Token/Language.pm b/lib/DDGC/DB/ResultSet/Token/Language.pm index 1db5d034d..ad2017427 100644 --- a/lib/DDGC/DB/ResultSet/Token/Language.pm +++ b/lib/DDGC/DB/ResultSet/Token/Language.pm @@ -10,10 +10,19 @@ sub untranslated { my ( $self, $token_domain_id, $language_id, $scalar_ignore_ids ) = @_; my @ignore_ids = $scalar_ignore_ids ? @{$scalar_ignore_ids} : (); $self->search({ - 'token_language_translations.id' => undef, 'token_domain_id' => $token_domain_id, 'language_id' => $language_id, - ($self->me.'id') => { -not_in => \@ignore_ids }, + -and => [ + 'me.id' => { -not_in => \@ignore_ids }, + -or => [ + 'me.id' => { -not_in => + $self->ddgc->rs('Token::Language::Translation')->search({ + check_result => '1', + },)->get_column('token_language_id')->as_query, + }, + fuzzy => 1, + ], + ], },{ join => [ { token_language_translations => 'token_language_translation_votes' diff --git a/lib/DDGC/Web/Controller/Translate.pm b/lib/DDGC/Web/Controller/Translate.pm index abbfe54b3..4ce04ddc4 100644 --- a/lib/DDGC/Web/Controller/Translate.pm +++ b/lib/DDGC/Web/Controller/Translate.pm @@ -190,8 +190,14 @@ sub domain :Chained('logged_in') :PathPart('') :CaptureArgs(1) { $c->stash->{token_domain_languages_rs} = $token_domain_language_rs->search({},{ '+columns' => { token_languages_undone_count => $c->d->rs('Token::Language')->search({ - 'token_language_translations.id' => undef, - 'undone_count.token_domain_language_id' => { -ident => 'me.id' }, + -and => [ + 'undone_count.id' => { -not_in => + $c->ddgc->rs('Token::Language::Translation')->search({ + check_result => '1', + },)->get_column('token_language_id')->as_query, + }, + 'undone_count.token_domain_language_id' => { -ident => 'me.id' }, + ], },{ join => 'token_language_translations', alias => 'undone_count' })->count_rs->as_query,