perlinternationalizationmoosecatalyst

Adding CatalystX::I18N::Maketext to my DBIC schema


sorry, I thought I had got there after my last post, however I only got as far as accessing from a separate PL file. I'm now trying to ensure I can load the lexicon with the schema load and not everytime I call a method in my result / resultset classes (which seems like a really terrible idea).

So to try and give a complete picture, here's the script I eventually got to work:

#!/usr/bin/perl

use strict;
use warnings;
use FindBin qw( $Bin );
use lib "$Bin/../lib";
use Data::Dumper::Concise;
use TopTable::Maketext;
use Config::ZOMG;
use Path::Class::Dir;

# Load the Catalyst config
my $tt_config = Config::ZOMG->new( name => "TopTable" );
my $config_hash = $tt_config->load;

# Load the locales from the config
my (@locales, %inheritance, $config);
$config = $config_hash->{I18N}{locales};
foreach my $locale (keys %$config) {
  push(@locales, $locale);
  $inheritance{$locale} = $config->{$locale}{inherits} if defined $config->{$locale}{inherits};
}

# Get the directory where the messages are defined
my $dir = Path::Class::Dir->new( "$Bin/..", "root", "locale" );

# Load the lexicon
TopTable::Maketext->load_lexicon(
  locales => \@locales,
  directories => [$dir],
  gettext_style => 1,
  inheritance => \%inheritance,
);

my $lang = TopTable::Maketext->get_handle( "en_GB" );
printf "%s\n", $lang->maketext( "menu.title.league-tables", "Division Three" );

1;

Here's my TopTable::Maketext:

package TopTable::Maketext;

use strict;
use warnings;
use parent qw(CatalystX::I18N::Maketext);

1;

Now here's my schema file:

use utf8;
package TopTable::Schema;

# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE

use Moose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Schema';

__PACKAGE__->load_namespaces;


# Created by DBIx::Class::Schema::Loader v0.07037 @ 2013-12-03 11:04:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:uMxbZipkwEqVJYByeZhY5Q


# You can replace this text with custom code or comments, and it will be preserved on regeneration
__PACKAGE__->meta->make_immutable(inline_constructor => 0);
1;

I'm very much a Moose novice I'm afraid, but believed if I added a 'lang' attribute with a builder method that sets all that up, I could then access that from my DB methods:

use utf8;
package TopTable::Schema;

# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE

use Moose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Schema';

__PACKAGE__->load_namespaces;


# Created by DBIx::Class::Schema::Loader v0.07037 @ 2013-12-03 11:04:44
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:uMxbZipkwEqVJYByeZhY5Q

use FindBin qw( $Bin );
use TopTable::Maketext;

has "lang" => (
  is => "ro",
  isa => "TopTable::Maketext",
  builder => "_set_maketext",
  required => 1,
);

sub _set_maketext {
  my ( $self ) = @_;
  my $class = $self->class;
  my $app = $self->_app;
  my (@locales, %inheritance);
  my $config = $app->config->{I18N}{locales};
  $app->log->debug( sprintf( "app: %s, class: %s", $app, $class ) );
  printf( "app: %s, class: %s", $app, $class );
  
  foreach my $locale (keys %$config) {
    push(@locales, $locale);
    $inheritance{$locale} = $config->{$locale}{inherits} if defined $config->{$locale}{inherits};
  }

  my $dir = Path::Class::Dir->new( "$Bin/..", "root", "locale" );
  TopTable::Maketext->load_lexicon(
    locales => \@locales,
    directories => [$dir],
    gettext_style => 1,
    inheritance => \%inheritance,
  );
  
  return TopTable::Maketext->get_handle( "en_GB" );
}

# You can replace this text with custom code or comments, and it will be preserved on regeneration
__PACKAGE__->meta->make_immutable(inline_constructor => 0);
1;

This however, doesn't work - the 'lang' method is accessible, but returns undef - I have added this as a test in one of my resultset methods: $logger->( "debug", $self->result_source->schema->lang->maketext("menu.title.league-tables", "Division Three") ); But this gives an error: [error] Caught exception in TopTable::Controller::Admin::Bans->process_form "Can't call method "maketext" on an undefined value at D:\Personal\Dev\Web\www.mkttl.co.uk\TopTable\lib/TopTable/Schema/ResultSet/Ban.pm line 88."

Grateful for any advice, thanks so much! I hope I've provided enough to see what's going on.


Solution

  • I have, with the very kind and patient assistance of @simbabque, managed to work this out.

    simbabque suggested I set the lang attribute to lazy, which did work:

    use FindBin qw( $Bin );
    use TopTable::Maketext;
    
    has "lang" => (
      is => "ro",
      isa => "TopTable::Maketext",
      builder => "_set_maketext",
      lazy => 1,
    );
    
    sub _set_maketext {
      my ( $self ) = @_;
      my $class = $self->class;
      my $app = $self->_app;
      my (@locales, %inheritance);
      my $config = $app->config->{I18N}{locales};
      $app->log->debug( sprintf( "app: %s, class: %s", $app, $class ) );
      printf( "app: %s, class: %s", $app, $class );
      
      foreach my $locale (keys %$config) {
        push(@locales, $locale);
        $inheritance{$locale} = $config->{$locale}{inherits} if defined $config->{$locale}{inherits};
      }
    
      my $dir = Path::Class::Dir->new( "$Bin/..", "root", "locale" );
      TopTable::Maketext->load_lexicon(
        locales => \@locales,
        directories => [$dir],
        gettext_style => 1,
        inheritance => \%inheritance,
      );
      
      return TopTable::Maketext->get_handle( "en_GB" );
    }
    
    

    This worked, but produced the message Lexicon has already been loaded for TopTable::Maketext, which suggested Catalyst loading the lexicon was a global action, so actually I was able to get the _set_maketext method down to:

    sub _set_maketext {
      return TopTable::Maketext->get_handle( "en_GB" );
    }
    

    So far so good, but I then had to work out how to get the user's locale into the call to get_handle().

    Again, with the help of simbabque, I have managed to get this working using an ACCEPT_CONTEXT sub in the model (note the if ref( $c ) eq "TopTable" check - as the comment says, the model seems to get called by Catalyst as part of the instantiation, before my code kicks in, and in these cases, $c is the string "TopTable", not a ref to the TopTable object so we can't call ->locale on it):

    package TopTable::Model::DB;
    
    use strict;
    use base 'Catalyst::Model::DBIC::Schema';
    
    __PACKAGE__->config(
        schema_class => 'TopTable::Schema',
        
        connect_info => {
            dsn => 'dbi:mysql:toptable',
            user => '',
            password => '',
        }
    );
    
    use TopTable::Maketext;
    
    sub ACCEPT_CONTEXT {
      my ( $self, $c ) = @_;
      
      # We have to check the ref of $c here because this model seems to get called by Catalyst as part of the instantiation, before my code kicks in
      # and in these cases, $c is the string "TopTable", not a ref to the TopTable object.
      $self->schema->_set_maketext( TopTable::Maketext->get_handle( $c->locale ) ) if ref( $c ) eq "TopTable";
      return $self;
    }
    
    1;
    

    I've changed the schema thus:

    use TopTable::Maketext;
    
    has "lang" => (
      is => "ro",
      isa => "TopTable::Maketext",
      writer => "_set_maketext",
    );
    

    This is now working as expected and simbabque's help was invaluable!