diff options
-rw-r--r-- | CHANGES | 162 | ||||
-rw-r--r-- | CHANGES.md | 176 | ||||
-rw-r--r-- | MANIFEST | 3 | ||||
-rw-r--r-- | Makefile.PL | 4 | ||||
-rw-r--r-- | README.md | 91 | ||||
-rwxr-xr-x | actions.sh | 40 | ||||
-rw-r--r-- | lib/JWebmail/Model/WriteMails.pm | 6 | ||||
-rw-r--r-- | lib/JWebmail/Plugin/I18N2.pm | 230 | ||||
-rw-r--r-- | t/I18N2.t | 31 |
9 files changed, 428 insertions, 315 deletions
diff --git a/CHANGES b/CHANGES deleted file mode 100644 index 7be26fd..0000000 --- a/CHANGES +++ /dev/null @@ -1,162 +0,0 @@ -Past and Upcoming Changes -========================= - -v1.0.0 release plan -------------------- -✓ consider renaming, relicensing - ✓ License - ✓ GPLv3+ and enter copyright info - * Maybe the translation/documentation can be made available under a different license - * may relicense this under the AGPL. - ✓ Rename - ✓ JWebmail -✓ make github ready - ✓ remove sensitive files (gitignore) - ✓ add git vcs - ✓ remove part of the english translation -* check legal requirements -✓ BUG: home not displaying -✓ show new messages per folder -✓ BUG: empty folder not displaying correctly -✓ better documentation - ✓ document i18n snippets - ✓ cleanup comments - ✓ list functionality for ReadMails#communicate - ✓ OMail - ✓ OMail::Helper - ✓ OMail::Controller::All - ✓ OMail::Plugin::I18N - ✓ OMail::Plugin::INIConfig - ✓ OMail::Plugin::ServerSideSessionData - ✓ OMail::Model::WriteMails - ✓ OMail::Model::ReadMails - ✓ OMail::Model::Driver::QMailAuthuser - ✓ OMail::Model::Driver::QMailAuthuser::Extract -✓ better pagination - ✓ BUG: pagination forward -> backward is shifting by 1 (page start needs to be decremented) - ✓ move out to helper - ✓ more generic names -✓ advance ini config plugin - ✓ set global section to global scope - ✓ introduce arrays - ✓ make nesting sections more explicit -✓ write more tests - ✓ test pagination - ✓ test mail_line - ✓ test for ini parser - ✓ basic test for application -✓ improve i18n - ✓ german translation - ✓ look into i18n configuration - ✓ remove TXT alias -✓ more configuration (for model) - ✓ disable cram - ✓ select mock read model - ✓ lazy init for mock model - ✓ add switch disabling message send - ✓ Extract: user to switch to - ✓ Extract: adjustable maildir directory -✓ read secret from config file -✓ Extract: configurable perl lib -✓ Extract: encoding issues -✓ improve session data security - ✓ use a server side cookie implementation - ✓ use a one time pad - ✓ resolve server/client session duration issues - ✓ use cryptographically secure random data - ✓ hide password length -✓ handle empty folders -✓ logging support for Extract.pm -✓ true perl 5.16 support -✓ cpan build and deploy script -✓ remove prefs -✓ file upload for attachment - ✓ file type detection - ✓ move WriteMails from Email::Simple to Email::MIME -✓ configuration as plugin (Mojo::Plugin::Config) -✓ model as helpers, initialized in startup -✓ send - ✓ multiple mails for cc etc. - * content-transfer encoding, research (currently 8bit) -✓ better design for send and read - ✓ send - ✓ read -✓ sandbox html mails -✓ i18n as ini files -✓ rework mail folders -✓ rewrite about -✓ search in subject - - -v1.1.0 release plan -------------------- -* INV: wrong subject being shown -* INV: new mails are not highlighted -* INV: displayheaders table does not fill outer container -✓ better back buttons - ✓ writemail - ✓ read mail -✓ improve server side session cleanup process coordination -✓ consider using Crypt::URandom instead of Crypt::Random -✓ factor out date format function -✓ add a delete session function for s3d, maybe - ✓ simply remove key from cookie -* separate development and production configuration -* create base configuration -* repurpose status field in displayheader - ✓ currently just renamed -* improve performance - * async read for extract - ✓ async version of driver - ✓ async version of model - * async version of controller - * async wait for send - * add wait_for_child to event loop -* consider using more mojo functions - ✓ use Mojolicious::Types to replace File::Type - ✓ Helper - * QMailAuthuser -* moving mails to other folders - * creating new folders - * backend -* add more mime types - * jpeg - * png - * gif - - -v1.2.0 release plan -------------------- -* advance ini config plugin - * allow non-leaf nodes to be arrays - * allow quotes - * allow continuation over multiple lines - * warn about overrides - * add template support, maybe -* improve i18n - * add localization of dates and time -* better pagination - * merge with partial templates, maybe -* improve performance, consider alternatives to Extract.pm - * based on Maildir::Light -* add config validation -* click on sender to answer -* mobile optimize -* download mail and attachments -* cleanup css -* allow multiple attachments -* consider using more mojo functions - * base64 - * encoding - * json - * filepaths - * dump - * mail? -* add mails to Sent folder - - -v1.3.0 release plan -------------------- -* smtp send model, maybe -* pop read model, maybe -* add icons for navigation diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..ca2d303 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,176 @@ +Past and Upcoming Changes +========================= + +v1.0.0 release plan +------------------- +- [x] consider renaming, relicensing + - [x] License + - [x] GPLv3+ and enter copyright info + - [ ] Maybe the translation/documentation can be made available under a + different license + - [ ] may relicense this under the AGPL. + - [x] Rename + - [x] JWebmail +- [x] make github ready + - [x] remove sensitive files (gitignore) + - [x] add git vcs + - [x] remove part of the english translation +- [ ] check legal requirements +- [x] BUG: home not displaying +- [x] show new messages per folder +- [x] BUG: empty folder not displaying correctly +- [x] better documentation + - [x] document i18n snippets + - [x] cleanup comments + - [x] list functionality for ReadMails#communicate + - [x] OMail + - [x] OMail::Helper + - [x] OMail::Controller::All + - [x] OMail::Plugin::I18N + - [x] OMail::Plugin::INIConfig + - [x] OMail::Plugin::ServerSideSessionData + - [x] OMail::Model::WriteMails + - [x] OMail::Model::ReadMails + - [x] OMail::Model::Driver::QMailAuthuser + - [x] OMail::Model::Driver::QMailAuthuser::Extract +- [x] better pagination + - [x] BUG: pagination forward -> backward is shifting by 1 + (page start needs to be decremented) + - [x] move out to helper + - [x] more generic names +- [x] advance ini config plugin + - [x] set global section to global scope + - [x] introduce arrays + - [x] make nesting sections more explicit +- [x] write more tests + - [x] test pagination + - [x] test mail_line + - [x] test for ini parser + - [x] basic test for application +- [x] improve i18n + - [x] german translation + - [x] look into i18n configuration + - [x] remove TXT alias +- [x] more configuration (for model) + - [x] disable cram + - [x] select mock read model + - [x] lazy init for mock model + - [x] add switch disabling message send + - [x] Extract: user to switch to + - [x] Extract: adjustable maildir directory +- [x] read secret from config file +- [x] Extract: configurable perl lib +- [x] Extract: encoding issues +- [x] improve session data security + - [x] use a server side cookie implementation + - [x] use a one time pad + - [x] resolve server/client session duration issues + - [x] use cryptographically secure random data + - [x] hide password length +- [x] handle empty folders +- [x] logging support for Extract.pm +- [x] true perl 5.16 support +- [x] cpan build and deploy script +- [x] remove prefs +- [x] file upload for attachment + - [x] file type detection + - [x] move WriteMails from Email::Simple to Email::MIME +- [x] configuration as plugin (Mojo::Plugin::Config) +- [x] model as helpers, initialized in startup +- [x] send + - [x] multiple mails for cc etc. + - [ ] content-transfer encoding, research (currently 8bit) +- [x] better design for send and read + - [x] send + - [x] read +- [x] sandbox html mails +- [x] i18n as ini files +- [x] rework mail folders +- [x] rewrite about +- [x] search in subject + +Current v1.1.0 +-------------- +- [ ] From v1.0.0 + - [ ] Maybe the translation/documentation can be made available under a + different license + - [ ] may relicense this under the AGPL. + - [ ] research content-transfer encoding for sending (currently 8bit) + - [ ] check legal requirements (cookies and DSGVO) +- [x] separate development and production configuration +- [x] better back buttons + - [x] writemail + - [x] read mail +- [x] improve server side session cleanup process coordination +- [x] consider using Crypt::URandom instead of Crypt::Random +- [x] factor out date format function +- [x] add a delete session function for s3d, maybe + - [x] simply remove key from cookie +- [x] add actions script +- [ ] repurpose status field in displayheader + - [x] currently just renamed +- [ ] advance ini config plugin + - [ ] allow non-leaf nodes to be arrays + - [ ] allow quotes + - [ ] allow continuation over multiple lines + - [ ] warn about overrides + - [ ] add template support, maybe +- [ ] improve i18n + - [ ] add localization of dates and time +- [ ] improve performance, consider alternatives to Extract.pm + - [ ] based on Maildir::Light + - [ ] reimplementation in Rust +- [x] refactor I18N plugin to allow independent provider +- [ ] refactor driver into a role +- [ ] merge read and row (with content type) +- [ ] fix tests +- [ ] consider using more mojo functions + - [x] use Mojolicious::Types to replace File::Type + - [x] Helper + - [ ] QMailAuthuser +- [ ] moving mails to other folders + - [ ] creating new folders + - [ ] backend +- [ ] specify protocol for backend interaction +- [ ] cleanup README + +Future +------ +- [ ] INV: wrong subject being shown +- [ ] INV: new mails are not highlighted +- [ ] INV: displayheaders table does not fill outer container +- [ ] consider using more mojo functions + - [ ] base64 + - [ ] encoding + - [ ] json + - [ ] filepaths + - [ ] dump + - [ ] mail? +- [ ] create base configuration +- [ ] improve performance + - [ ] async read for extract + - [x] async version of driver + - [x] async version of model + - [ ] async version of controller + - [ ] async wait for send + - [ ] add wait_for_child to event loop +- [ ] add more mime types to read + - [ ] jpeg + - [ ] png + - [ ] gif +- [ ] better pagination + - [ ] merge with partial templates, maybe +- [ ] add config validation +- [ ] click on sender to answer +- [ ] mobile optimize +- [ ] download mail and attachments +- [ ] cleanup css +- [ ] allow multiple attachments +- [ ] add mails to Sent folder +- [ ] smtp send model, maybe +- [ ] pop read model, maybe +- [ ] add icons for navigation +- [ ] allow changing password +- [ ] think about forgot password feature +- [ ] address book support + - [ ] add links on email addresses in header : click = add into addressbook @@ -11,7 +11,7 @@ lib/JWebmail/Plugin/INIConfig.pm lib/JWebmail/Plugin/I18N.pm lib/JWebmail/Plugin/I18N2.pm lib/JWebmail/Plugin/ServerSideSessionData.pm -README +README.md LICENSE lang/de.lang lang/en.lang @@ -20,7 +20,6 @@ t/Webmail.t t/INI.t t/Helper.t script/jwebmail -CREDITS MANIFEST public/style.css templates/headers/_display_top_nav.html.ep diff --git a/Makefile.PL b/Makefile.PL index 3633f63..b51c6b4 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -8,7 +8,7 @@ WriteMakefile( MIN_PERL_VERSION => 'v5.22', NAME => 'JWebmail', VERSION_FROM => 'lib/JWebmail.pm', - LICENSE => 'GPL', + LICENSE => 'gpl_3', PREREQ_PM => { 'Mojolicious' => '8.64', 'Config::Tiny' => 'v2.24', @@ -17,4 +17,4 @@ WriteMakefile( 'Mail::Box::Manager' => 'v3.9', }, test => {TESTS => 't/*.t'} -); +) @@ -1,81 +1,74 @@ JWebmail ======== -This is a rewrite of oMail by Oliver Müller <om@omnis.ch>. +This is based on concepts of oMail by Oliver Müller <om@omnis.ch>. -oMail has not seen much progress in the last two decades -so my <jannis@fehcom.de> goal is to bring it up to date. -OMail was tied to qmail. JWebmail is not so tightly bound. +JWebmail is a webmail server build on top of Mojolicious. This includes: -- Using a perl web framework and leave the deprecated CGI behind. - You can still use it in a cgi setup if you like but you now - have the option of plack/psgi and fcgi as well as the - build in server hypnotoad. +- Using a Perl web framework and leave the deprecated CGI behind. You can + still use it in a CGI setup if you like but you now have the option of + plack/psgi and fcgi as well as the build in server hypnotoad. - Set up a MVC architecture instead of a single file. -- Improve security by only running a small part of the - model with elevated privileges. -- Make sure it works well with sqmail and its authuser - authenticator and maildir but also permit other setups - (currently not supported but adding more should be easy). - Maybe I even add a POP or IMAP based backends instead +- Improve security by only running a small part of the model with elevated + privileges. +- Make sure it works well with sqmail and its authuser authenticator and + maildir but also permit other setups (currently not supported but adding + more should be easy). Maybe I even add a POP or IMAP based back-ends instead of reading them from disk. ## License -JWebmail is available under the GNU General Public License -version 3 or later. +JWebmail is available under the GNU General Public License version 3. ## JWebmail-webmail - INSTALL -You still need to install sqmail and setup -an external web server. - -## Future feature list -- [ ] address book support - -### Read -- [ ] bounce -- [ ] add links on email addresses in header : click = add into addressbook +You need a moderately new version of Perl (v5.22+) +You need access to CPAN. +You need to install sqmail. +It is recommended to use an external web server like Apache or Nginx. Posts ----- -* Complain about IPC::Open2 ignoring 'open' pragma -* Complain about undef references causing errors, and non-fine granular switch no strict 'refs' -* Thank for perldoc.org +- Complain about IPC::Open2 ignoring 'open' pragma +- Complain about undef references causing errors, and non-fine granular + switch no strict 'refs' +- Thank for perldoc.org I18N patch url_for ------------------ -I have taken the monkey patching approach that was taken by Mojolicious::Plugin::I18N. -I used `can` to get the old method for looser coupling and used the Mojo::Util::monkey_patch -instead of manually doing it. This is probably overkill. - -I'm desperately looking for a different approach. Yeah the monkey patching works great, -but it violates the open-closed principal. But I cannot find an appropriate alternative. -Extending the controller and overriding url_for does not work cleanly as the user directly -inherits from Mojolicious::Controller. -Also going the 'better' approach of solving at the root by using an extension of -Mojolicious::Routes::Match->path_for and supplying it to the controller by setting -match does not work as it is on the one hand extremely difficult to inject it in -the first place and the attribute is overwritten in the dispatching process anyways. -One issue when taking the Match approach is that it needs knowledge of the stash -values which can cause cyclic references. +I have taken the monkey patching approach that was taken by +Mojolicious::Plugin::I18N. I used `can` to get the old method for looser +coupling and used the Mojo::Util::monkey_patch instead of manually doing it. +This is probably overkill. + +I'm desperately looking for a different approach. Yeah the monkey patching +works great, but it violates the open-closed principal. But I cannot find +an appropriate alternative. Extending the controller and overriding url_for +does not work cleanly as the user directly inherits from Mojolicious::Controller. +Also going the 'better' approach of solving at the root by using an extension +of Mojolicious::Routes::Match->path_for and supplying it to the controller +by setting match does not work as it is on the one hand extremely difficult +to inject it in the first place and the attribute is overwritten in the +dispatching process anyways. One issue when taking the Match approach is +that it needs knowledge of the stash values which can cause cyclic references. I thought of three approaches injecting the modified Match instance into the class: 1. Extending the Mojolicious::Controller and overriding the new method. This has the issue that inheritance is static but one can use Roles that are dynamically consumed. -2. Overriding build_controller in Mojolicious. To make this cleanly it needs to be monkey patched - by the plugin which is exactly what we want to avoid. :( -3. The matcher can be set in a hook relatively early in its lifetime - and hooks compose well. - -A completely different option is to use the router directly and register a global -route that has the language as parameter. But omitting the language leads to problems. +2. Overriding build_controller in Mojolicious. To make this cleanly it needs + to be monkey patched by the plugin which is exactly what we want to avoid. :( +3. The matcher can be set in a hook relatively early in its lifetime and + hooks compose well. + +A completely different option is to use the router directly and register a +global route that has the language as parameter. But omitting the language +leads to problems. One can use a redirect on root. Very easy but also not very effective. diff --git a/actions.sh b/actions.sh new file mode 100755 index 0000000..49fa4e2 --- /dev/null +++ b/actions.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env sh + +set -euC + +test () { + eval "prove -l ${1-} t/" +} + +start () { + script/jwebmail daemon +} + +logrotate () { + mode=${1:-development} + mv -i "log/$mode.log" "log/${mode}_$(date --iso-8601=minutes).log" +} + +linelength () { + files=${1:-'README.md CHANGES LICENSE'} + for file in $files + do + fold -s -w 85 "$file" | diff "$file" - + done +} + +follow () { + mode=${1:-development} + tail -f "log/$mode.log" +} + +check_manifest () { + perl -nE 'chomp; say unless -e' MANIFEST +} + +cmd=$1 +shift +if [ "$(command -v "$cmd")" = "$cmd" ] +then eval "$cmd" "$@" +else echo "unkown commad '$cmd'"; exit 1 +fi diff --git a/lib/JWebmail/Model/WriteMails.pm b/lib/JWebmail/Model/WriteMails.pm index aa2f1d4..7a50bcb 100644 --- a/lib/JWebmail/Model/WriteMails.pm +++ b/lib/JWebmail/Model/WriteMails.pm @@ -69,8 +69,10 @@ sub sendmail { push @recipients, @{ $mail->{cc} } if $mail->{cc}; push @recipients, @{ $mail->{bcc} } if $mail->{bcc}; - say $mime if $Block_Writes; - return 1 if $Block_Writes; + if ($Block_Writes) { + say $mime; + return 1; + } return _send($mime, @recipients); } diff --git a/lib/JWebmail/Plugin/I18N2.pm b/lib/JWebmail/Plugin/I18N2.pm index 5084c97..35367e9 100644 --- a/lib/JWebmail/Plugin/I18N2.pm +++ b/lib/JWebmail/Plugin/I18N2.pm @@ -2,11 +2,65 @@ package JWebmail::Plugin::I18N2; use Mojo::Base 'Mojolicious::Plugin'; -use Mojolicious::Controller; -use Mojo::File; -use Mojo::Util 'monkey_patch'; -use Config::Tiny; +package JWebmail::Plugin::I18N2::Translator { + + use Mojo::File; + + use Config::Tiny; + + sub new { + my $cls = shift; + my $conf = @_ == 1 ? shift : {@_}; + my $self = {}; + + my @languages = keys %{$conf->{languages} // {}}; + + unless (@languages) { + @languages = map { s|^.*/(..)\.lang$|$1|r } glob("'$conf->{directory}/*.lang'"); + } + + # load languages + for my $l (@languages) { + if (my $dict = __loadi18n($conf->{directory}, $l)) { + $self->{$l} = $dict; + } + } + + return bless $self, $cls; + } + + sub languages { + my $self = shift; + if (@_) { + return exists $self->{$_[0]}; + } + return wantarray ? sort keys $self->%* : scalar keys $self->%*; + } + + sub translate { + my $self = shift; + my $lang = shift; + my $word = shift; + return $self->{$lang}{$word}; + } + + sub __loadi18n { + my $langsubdir = shift; + my $lang = shift; + + my $langFile = "$langsubdir/$lang.lang"; + my $TXT; + + if ( -f $langFile ) { + $TXT = Config::Tiny->read($langFile, 'utf8')->{'_'}; + if ($@) { + die "error reading file $langFile: $@"; + } + } + return $TXT; + } +} package JWebmail::Plugin::I18N2::Match { @@ -14,83 +68,85 @@ package JWebmail::Plugin::I18N2::Match { has '_i18n2_stash'; + sub __add_option_no_override { + my $key = shift; + my $value = shift; + + if (ref $_[0] eq 'HASH' && @_ == 1) { + $_[0]->{$key} ||= $value; + } + elsif (ref $_[1] eq 'HASH' && @_ == 2) { + $_[1]->{$key} ||= $value; + } + else { + my ($dom, %args) = @_%2 == 0 ? (undef, @_) : @_; + $args{$key} ||= $value; + @_ = ($dom, %args); + shift @_ unless defined $_[0]; + } + return @_; + } + sub path_for { my $self = shift; + my @args = @_; if (my $lang = $self->_i18n2_stash->{lang}) { - if (ref $_[0] eq 'HASH') { - $_[0]->{lang} ||= $lang; - } - elsif (ref $_[1] eq 'HASH') { - $_[1]->{lang} ||= $lang; - } - else { - my ($dom, %args) = @_%2 == 0 ? (undef, @_) : @_; - $args{lang} ||= $lang; - @_ = ($dom, %args); - shift @_ unless defined $_[0]; - } + @args = __add_option_no_override(lang => $lang, @args); } - $self->next::method(@_) + return $self->SUPER::path_for(@args); } } -has '_language_loaded' => sub { {} }; - - sub register { my ($self, $app, $conf) = @_; $conf //= {}; my $i18n_log = $app->log->context('[' . __PACKAGE__ . ']'); - # config - # 1. what languages - # 2. where are the files - # 3. fallback language - # - # look for languages automatically + my $translator = $conf->{translator} || sub { JWebmail::Plugin::I18N2::Translator->new(@_) }; my $defaultLang = $conf->{default_language} || 'en'; my $fileLocation = $conf->{directory} && Mojo::File->new($conf->{directory})->is_abs - ? $conf->{directory} - : $app->home->child($conf->{directory} || 'lang'); - my @languages = keys %{$conf->{languages} // {}}; + ? $conf->{directory} + : $app->home->child($conf->{directory} || 'lang'); - unless (@languages) { - @languages = map { $_ =~ s|^.*/(..)\.lang$|$1|r } glob("$fileLocation/*.lang"); - } - - $app->defaults(languages => [@languages]); - - # load languages - my $TXT; - for my $l (@languages) { - $TXT->{$l} = _loadi18n($fileLocation, $l, $i18n_log); - } + my $t = $translator->( + default_language => $defaultLang, + directory => $fileLocation, + %{$conf->{rest} // {}} + ); { local $" = ','; - $i18n_log->debug("loaded languages (@languages)"); + $i18n_log->debug("loaded languages (@{[$t->languages]})"); + + if (keys $conf->{languages}->%* > $t->languages) { + $i18n_log->warn("missing languages"); + } } - $self->_language_loaded( { map { $_ => 1 } @languages } ); + $app->defaults(default_language => $defaultLang); + $app->defaults(languages => [$t->languages]); # add translator as helper - my $i18n = sub { - my ($lang, $word) = @_; - $TXT->{$lang}{$word} || scalar( - local $" = ' ', - $lang && $word ? $app->log->debug('[' . __PACKAGE__ . "] missing translation for $lang:$word @{[ caller(2) ]}[0..2]") : (), - '', - ) - }; - $app->helper( l => sub { my $c = shift; $i18n->($c->stash->{lang}, shift) } ); + $app->helper(l => sub { + my $c = shift; + my $lang = @_ == 2 ? $_[0] : $c->stash->{lang}; + my $word = @_ == 2 ? $_[1] : $_[0]; + + my $res = $t->translate($lang, $word); + unless ($res) { + local $" = ' '; + $app->log->warn('[' . __PACKAGE__ . "] missing translation for '$lang':'$word' @{[ caller(1) ]}[0..2]"); + } + return $res; + }); # modify incoming url $app->hook(before_dispatch => sub { my $c = shift; unshift @{ $c->req->url->path->parts }, '' - unless $self->_language_loaded->{$c->req->url->path->parts->[0] || ''}; + unless $t->languages($c->req->url->path->parts->[0] || ''); }); # modify generated url @@ -105,29 +161,6 @@ sub register { return $app->routes->any('/:lang' => {lang => $defaultLang}); } - -sub _loadi18n { - - my $langsubdir = shift; - my $lang = shift; - my $log = shift; - - my $langFile = "$langsubdir/$lang.lang"; - my $TXT; - - if ( -f $langFile ) { - $TXT = Config::Tiny->read($langFile, 'utf8')->{'_'}; - if ($@ || !defined $TXT) { - $log->error("error reading file $langFile: $@"); - } - } - else { - $log->warn("language file $langFile does not exist!"); - } - return $TXT; -} - - 1 __END__ @@ -140,32 +173,32 @@ JWebmail::Plugin::I18N2 - Custom Made I18N Support an alternative to JWebmail::P =head1 SYNOPSIS - $app->plugin('I18N2', { - languages => [qw(en de es)], - default_language => 'en', - directory => '/path/to/language/files/', - }) + $app->plugin('I18N2', { + languages => [qw(en de es)], + default_language => 'de', + directory => 'path/to/language/files/', + }) - # in your controller - $c->l('hello') + # in your controller + $c->l('hello') - # in your templates - <%= l 'hello' %> + # in your templates + <%= l 'hello' %> - @@ de.lang - login = anmelden - userid = nuzerkennung - passwd = passwort - failed = fehlgeschlagen - about = über + @@ de.lang + login = anmelden + userid = nuzerkennung + passwd = passwort + failed = fehlgeschlagen + about = über - example.com/de/myroute # $c->stash('lang') eq 'de' - example.com/myroute # $c->stash('lang') eq $defaultLanguage + example.com/de/myroute # $c->stash('lang') eq 'de' + example.com/myroute # $c->stash('lang') eq $defaultLanguage - # on example.com/de/myroute - url_for('my_other_route') #=> example.com/de/my_other_route + # on example.com/de/myroute + url_for('my_other_route') #=> example.com/de/my_other_route - url_for('my_other_route', lang => 'es') #=> example.com/es/my_other_route + url_for('my_other_route', lang => 'es') #=> example.com/es/my_other_route =head1 DESCRIPTION @@ -177,6 +210,8 @@ Be carefult with colliding routes. Mojolicious::Controller::url_for is patched so that the current language will be kept for router named urls. +This Plugin only works with Mojolicious version 8.64 or higher. + =head1 OPTIONS =head2 default_language @@ -190,7 +225,7 @@ Directory to look for language files. =head2 languages List of allowed languages. -Files of the pattern "$lang.lang" will be looked for. +As a default, files of the pattern "$lang.lang" will be looked for. =head1 HELPERS @@ -198,7 +233,6 @@ Files of the pattern "$lang.lang" will be looked for. This is used for your translations. - $c->l('hello') - $app->helper('hello')->() + $c->l('hello') =cut diff --git a/t/I18N2.t b/t/I18N2.t new file mode 100644 index 0000000..f9c6285 --- /dev/null +++ b/t/I18N2.t @@ -0,0 +1,31 @@ +use v5.22; +use warnings; +use utf8; + +use Test::More; + +use JWebmail::Plugin::I18N2; + +*add_option = \&JWebmail::Plugin::I18N2::Match::__add_option_no_override; + + +subtest 'add_option' => sub { + + is_deeply(add_option(a => 1, {}), {a => 1}, "empty-hash"); + is_deeply(add_option(a => 5, {a => 1}), {a => 1}, "no-add-hash"); + is_deeply(add_option(b => 2, {a => 1}), {a => 1, b => 2}, "add-hash"); + + is_deeply([add_option(a => 1, 'arg', {})], ['arg', {a => 1}], "empty-arg-hash"); + is_deeply([add_option(a => 5, 'arg', {a => 1})], ['arg', {a => 1}], "no-add-arg-hash"); + is_deeply([add_option(b => 2, 'arg', {a => 1})], ['arg', {a => 1, b => 2}], "add-arg-hash"); + + is_deeply([add_option(a => 1)], [a => 1], "empty-array"); + is_deeply([add_option(a => 5, a => 1)], [a => 1], "no-add-array"); + eq_set([add_option(b => 2, a => 1)], [a => 1, b => 2], "add-array"); + + is_deeply([add_option(a => 1, 'arg')], ['arg', a => 1], "empty-arg-array"); + is_deeply([add_option(a => 5, 'arg', a => 1)], ['arg', a => 1], "no-add-arg-array"); + eq_set([add_option(b => 2, 'arg', a => 1)], ['arg', a => 1, b => 2], "add-arg-array"); +}; + +done_testing; |