mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-09-12 05:33 am

[dw-free] Code Refactoring

[commit: http://hg.dwscoalition.org/dw-free/rev/e8d5bfb23489]

http://bugs.dwscoalition.org/show_bug.cgi?id=1726

Refactor ljlang.pl to LJ::Lang module.

Patch by [personal profile] szabgab.

Files modified:
  • bin/upgrading/texttool.pl
  • bin/worker/esn-cluster-subs
  • bin/worker/esn-cluster-subs-mass
  • bin/worker/esn-filter-subs
  • bin/worker/esn-filter-subs-mass
  • bin/worker/esn-fired-event
  • bin/worker/esn-fired-event-mass
  • bin/worker/esn-process-sub
  • bin/worker/esn-process-sub-mass
  • bin/worker/paidstatus
  • bin/worker/process-esn
  • bin/worker/process-esn-mass
  • cgi-bin/DW/BusinessRules/InviteCodes.pm
  • cgi-bin/DW/Worker/DistributeInvites.pm
  • cgi-bin/DW/Worker/XPostWorker.pm
  • cgi-bin/LJ/Lang.pm
  • cgi-bin/ljlang.pl
  • cgi-bin/modperl_subs.pl
  • t/commafy.t
  • t/console-faqcat.t
  • t/faq.t
  • t/settings.t
--------------------------------------------------------------------------------
diff -r 0b01e0370322 -r e8d5bfb23489 bin/upgrading/texttool.pl
--- a/bin/upgrading/texttool.pl	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/upgrading/texttool.pl	Sat Sep 12 05:33:57 2009 +0000
@@ -59,7 +59,7 @@ unless (-d $ENV{'LJHOME'}) {
         "You must fix this before you can run this database update script.";
 }
 require "$ENV{'LJHOME'}/cgi-bin/ljlib.pl";
-require "$ENV{'LJHOME'}/cgi-bin/ljlang.pl";
+use LJ::Lang;
 require "$ENV{'LJHOME'}/cgi-bin/weblib.pl";
 
 my %dom_id;     # number -> {}
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-cluster-subs
--- a/bin/worker/esn-cluster-subs	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-cluster-subs	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-cluster-subs-mass
--- a/bin/worker/esn-cluster-subs-mass	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-cluster-subs-mass	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-filter-subs
--- a/bin/worker/esn-filter-subs	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-filter-subs	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-filter-subs-mass
--- a/bin/worker/esn-filter-subs-mass	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-filter-subs-mass	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-fired-event
--- a/bin/worker/esn-fired-event	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-fired-event	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-fired-event-mass
--- a/bin/worker/esn-fired-event-mass	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-fired-event-mass	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-process-sub
--- a/bin/worker/esn-process-sub	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-process-sub	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/esn-process-sub-mass
--- a/bin/worker/esn-process-sub-mass	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/esn-process-sub-mass	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/paidstatus
--- a/bin/worker/paidstatus	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/paidstatus	Sat Sep 12 05:33:57 2009 +0000
@@ -22,7 +22,7 @@ use Time::HiRes qw/ gettimeofday tv_inte
 
 require 'ljlib.pl';
 require 'ljmail.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use DW::Shop;
 use DW::Shop::Cart;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/process-esn
--- a/bin/worker/process-esn	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/process-esn	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 bin/worker/process-esn-mass
--- a/bin/worker/process-esn-mass	Sat Sep 12 12:14:17 2009 +0800
+++ b/bin/worker/process-esn-mass	Sat Sep 12 05:33:57 2009 +0000
@@ -3,7 +3,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
 require 'ljprotocol.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 use LJ::Worker::TheSchwartz;
 use LJ::ESN;
 
diff -r 0b01e0370322 -r e8d5bfb23489 cgi-bin/DW/BusinessRules/InviteCodes.pm
--- a/cgi-bin/DW/BusinessRules/InviteCodes.pm	Sat Sep 12 12:14:17 2009 +0800
+++ b/cgi-bin/DW/BusinessRules/InviteCodes.pm	Sat Sep 12 05:33:57 2009 +0000
@@ -22,7 +22,7 @@ use List::Util ();
 use List::Util ();
 use lib "$LJ::HOME/cgi-bin";
 use base 'DW::BusinessRules';
-BEGIN { require "ljlang.pl"; }
+use LJ::Lang;
 
 =head1 NAME
 
diff -r 0b01e0370322 -r e8d5bfb23489 cgi-bin/DW/Worker/DistributeInvites.pm
--- a/cgi-bin/DW/Worker/DistributeInvites.pm	Sat Sep 12 12:14:17 2009 +0800
+++ b/cgi-bin/DW/Worker/DistributeInvites.pm	Sat Sep 12 05:33:57 2009 +0000
@@ -26,8 +26,9 @@ use DW::InviteCodeRequests;
 use DW::InviteCodeRequests;
 use DW::BusinessRules::InviteCodes;
 use LJ::User;
+use LJ::Lang;
 
-BEGIN { require "ljlang.pl"; require "ljmail.pl"; require "sysban.pl"; }
+BEGIN { require "ljmail.pl"; require "sysban.pl"; }
 
 sub schwartz_capabilities { return ('DW::Worker::DistributeInvites'); }
 
diff -r 0b01e0370322 -r e8d5bfb23489 cgi-bin/DW/Worker/XPostWorker.pm
--- a/cgi-bin/DW/Worker/XPostWorker.pm	Sat Sep 12 12:14:17 2009 +0800
+++ b/cgi-bin/DW/Worker/XPostWorker.pm	Sat Sep 12 05:33:57 2009 +0000
@@ -27,8 +27,9 @@ use DW::External::Account;
 use DW::External::Account;
 use LJ::Event::XPostSuccess;
 use LJ::User;
+use LJ::Lang;
 
-BEGIN { require "ljlang.pl"; require "ljprotocol.pl" }
+BEGIN { require "ljprotocol.pl" }
 
 sub schwartz_capabilities { return ('DW::Worker::XPostWorker'); }
 
diff -r 0b01e0370322 -r e8d5bfb23489 cgi-bin/LJ/Lang.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/LJ/Lang.pm	Sat Sep 12 05:33:57 2009 +0000
@@ -0,0 +1,837 @@
+package LJ::Lang;
+use strict;
+
+use Class::Autouse qw(
+
+                      LJ::LangDatFile
+                      );
+
+
+use constant MAXIMUM_ITCODE_LENGTH => 80;
+
+my @day_short   = (qw[Sun Mon Tue Wed Thu Fri Sat]);
+my @day_long    = (qw[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]);
+my @month_short = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]);
+my @month_long  = (qw[January February March April May June July August September October November December]);
+
+# For init_cvsprefixes(), langdat_file_of_lang_itcode(), and stuff.
+my @shared_cvsprefixes = qw(cvs/livejournal cvs/dw-free);
+my @local_cvsprefixes = qw(cvs/local cvs/dw-nonfree);
+my ($cvspfx_shared, $cvspfx_local);
+
+# get entire array of days and months
+sub day_list_short   { return @LJ::Lang::day_short;   }
+sub day_list_long    { return @LJ::Lang::day_long;    }
+sub month_list_short { return @LJ::Lang::month_short; }
+sub month_list_long  { return @LJ::Lang::month_long;  }
+
+# access individual day or month given integer
+sub day_short   { return   $day_short[$_[0] - 1]; }
+sub day_long    { return    $day_long[$_[0] - 1]; }
+sub month_short { return $month_short[$_[0] - 1]; }
+sub month_long  { return  $month_long[$_[0] - 1]; }
+
+# lang codes for individual day or month given integer
+sub day_short_langcode   { return "date.day."   . lc(LJ::Lang::day_long(@_))    . ".short"; }
+sub day_long_langcode    { return "date.day."   . lc(LJ::Lang::day_long(@_))    . ".long";  }
+sub month_short_langcode { return "date.month." . lc(LJ::Lang::month_long(@_))  . ".short"; }
+sub month_long_langcode  { return "date.month." . lc(LJ::Lang::month_long(@_))  . ".long";  }
+
+# Translated names for individual day or month given integer. You probably want
+# these, not the ones above.
+sub day_short_ml { return LJ::Lang::ml( LJ::Lang::day_short_langcode( @_ ) ); }
+sub day_long_ml { return LJ::Lang::ml( LJ::Lang::day_long_langcode( @_ ) ); }
+sub month_short_ml { return LJ::Lang::ml( LJ::Lang::month_short_langcode( @_ ) ); }
+sub month_long_ml { return LJ::Lang::ml( LJ::Lang::month_long_langcode( @_ ) ); }
+
+## ordinal suffix
+sub day_ord {
+    my $day = shift;
+
+    # teens all end in 'th'
+    if ($day =~ /1\d$/) { return "th"; }
+
+    # otherwise endings in 1, 2, 3 are special
+    if ($day % 10 == 1) { return "st"; }
+    if ($day % 10 == 2) { return "nd"; }
+    if ($day % 10 == 3) { return "rd"; }
+
+    # everything else (0,4-9) end in "th"
+    return "th";
+}
+
+sub time_format
+{
+    my ($hours, $h, $m, $formatstring) = @_;
+
+    if ($formatstring eq "short") {
+        if ($hours == 12) {
+            my $ret;
+            my $ap = "a";
+            if ($h == 0) { $ret .= "12"; }
+            elsif ($h < 12) { $ret .= ($h+0); }
+            elsif ($h == 12) { $ret .= ($h+0); $ap = "p"; }
+            else { $ret .= ($h-12); $ap = "p"; }
+            $ret .= sprintf(":%02d$ap", $m);
+            return $ret;
+        } elsif ($hours == 24) {
+            return sprintf("%02d:%02d", $h, $m);
+        }
+    }
+    return "";
+}
+
+#### ml_ stuff:
+my $LS_CACHED = 0;
+my %DM_ID = ();     # id -> { type, args, dmid, langs => { => 1, => 0, => 1 } }
+my %DM_UNIQ = ();   # "$type/$args" => ^^^
+my %LN_ID = ();     # id -> { ..., ..., 'children' => [ $ids, .. ] }
+my %LN_CODE = ();   # $code -> ^^^^
+my $LAST_ERROR;
+my %TXT_CACHE;
+
+sub last_error
+{
+    return $LAST_ERROR;
+}
+
+sub set_error
+{
+    $LAST_ERROR = $_[0];
+    return 0;
+}
+
+sub get_lang
+{
+    my $code = shift;
+    load_lang_struct() unless $LS_CACHED;
+    return $LN_CODE{$code};
+}
+
+sub get_lang_id
+{
+    my $id = shift;
+    load_lang_struct() unless $LS_CACHED;
+    return $LN_ID{$id};
+}
+
+sub get_dom
+{
+    my $dmcode = shift;
+    load_lang_struct() unless $LS_CACHED;
+    return $DM_UNIQ{$dmcode};
+}
+
+sub get_dom_id
+{
+    my $dmid = shift;
+    load_lang_struct() unless $LS_CACHED;
+    return $DM_ID{$dmid};
+}
+
+sub get_domains
+{
+    load_lang_struct() unless $LS_CACHED;
+    return values %DM_ID;
+}
+
+sub get_root_lang
+{
+    my $dom = shift;  # from, say, get_dom
+    return undef unless ref $dom eq "HASH";
+
+    my $lang_override = LJ::run_hook("root_lang_override", $dom);
+    return get_lang($lang_override) if $lang_override;
+
+    foreach (keys %{$dom->{'langs'}}) {
+        if ($dom->{'langs'}->{$_}) {
+            return get_lang_id($_);
+        }
+    }
+    return undef;
+}
+
+sub load_lang_struct
+{
+    return 1 if $LS_CACHED;
+    my $dbr = LJ::get_db_reader();
+    return set_error("No database available") unless $dbr;
+    my $sth;
+
+    $sth = $dbr->prepare("SELECT dmid, type, args FROM ml_domains");
+    $sth->execute;
+    while (my ($dmid, $type, $args) = $sth->fetchrow_array) {
+        my $uniq = $args ? "$type/$args" : $type;
+        $DM_UNIQ{$uniq} = $DM_ID{$dmid} = {
+            'type' => $type, 'args' => $args, 'dmid' => $dmid,
+            'uniq' => $uniq,
+        };
+    }
+
+    $sth = $dbr->prepare("SELECT lnid, lncode, lnname, parenttype, parentlnid FROM ml_langs");
+    $sth->execute;
+    while (my ($id, $code, $name, $ptype, $pid) = $sth->fetchrow_array) {
+        $LN_ID{$id} = $LN_CODE{$code} = {
+            'lnid' => $id,
+            'lncode' => $code,
+            'lnname' => $name,
+            'parenttype' => $ptype,
+            'parentlnid' => $pid,
+        };
+    }
+    foreach (values %LN_CODE) {
+        next unless $_->{'parentlnid'};
+        push @{$LN_ID{$_->{'parentlnid'}}->{'children'}}, $_->{'lnid'};
+    }
+
+    $sth = $dbr->prepare("SELECT lnid, dmid, dmmaster FROM ml_langdomains");
+    $sth->execute;
+    while (my ($lnid, $dmid, $dmmaster) = $sth->fetchrow_array) {
+        $DM_ID{$dmid}->{'langs'}->{$lnid} = $dmmaster;
+    }
+
+    $LS_CACHED = 1;
+}
+
+sub init_cvsprefixes {
+    return if defined($cvspfx_shared);
+
+    foreach my $p (@shared_cvsprefixes) {
+        if (-d "$LJ::HOME/$p") {
+            $cvspfx_shared = $p;
+            last;
+        }
+    }
+    $cvspfx_shared ||= "";
+
+    foreach my $p (@local_cvsprefixes) {
+        if (-d "$LJ::HOME/$p") {
+            $cvspfx_local = $p;
+            last;
+        }
+    }
+    $cvspfx_local ||= "";
+}
+
+sub langdat_file_of_lang_itcode
+{
+    my ($lang, $itcode, $want_cvs) = @_;
+
+    my $langdat_file = LJ::Lang::relative_langdat_file_of_lang_itcode($lang, $itcode);
+    my $cvs_extra = "";
+    if ($want_cvs) {
+        init_cvsprefixes();
+        if ($lang eq "en") {
+            $cvs_extra = "/$cvspfx_shared";
+        } else {
+            $cvs_extra = "/$cvspfx_local";
+        }
+    }
+    return "$LJ::HOME$cvs_extra/$langdat_file";
+}
+
+sub relative_langdat_file_of_lang_itcode
+{
+    my ($lang, $itcode) = @_;
+
+    my $root_lang = "en";
+    my $root_lang_local = $LJ::DEFAULT_LANG;
+
+    my $base_file = "bin/upgrading/$lang\.dat";
+
+    # not a root or root_local lang, just return base file location
+    unless ($lang eq $root_lang || $lang eq $root_lang_local) {
+        return $base_file;
+    }
+
+    my $is_local = $lang eq $root_lang_local;
+
+    # is this a filename-based itcode?
+    if ($itcode =~ m!^(/.+\.bml)!) {
+        my $file = $1;
+
+        # given the filename of this itcode and the current
+        # source, what langdat file should we use?
+        my $langdat_file = "htdocs$file\.text";
+        $langdat_file .= $is_local ? ".local" : "";
+        return $langdat_file;
+    }
+
+    # not a bml file, goes into base .dat file
+    return $base_file;
+}
+
+sub itcode_for_langdat_file {
+    my ($langdat_file, $itcode) = @_;
+
+    # non-bml itcode, return full itcode path
+    unless ($langdat_file =~ m!^/.+\.bml\.text(?:\.local)?$!) {
+        return $itcode;
+    }
+
+    # bml itcode, strip filename and return
+    if ($itcode =~ m!^/.+\.bml(\..+)!) {
+        return $1;
+    }
+
+    # fallback -- full $itcode
+    return $itcode;
+}
+
+sub get_chgtime_unix
+{
+    my ($lncode, $dmid, $itcode) = @_;
+    load_lang_struct() unless $LS_CACHED;
+
+    $dmid = int($dmid || 1);
+
+    my $l = get_lang($lncode) or return "No lang info for lang $lncode";
+    my $lnid = $l->{'lnid'}
+        or die "Could not get lang_id for lang $lncode";
+
+    my $itid = LJ::Lang::get_itemid($dmid, $itcode)
+        or return 0;
+
+    my $dbr = LJ::get_db_reader();
+    $dmid += 0;
+    my $chgtime = $dbr->selectrow_array("SELECT chgtime FROM ml_latest WHERE dmid=? AND itid=? AND lnid=?",
+                                        undef, $dmid, $itid, $lnid);
+    die $dbr->errstr if $dbr->err;
+    return $chgtime ? LJ::mysqldate_to_time($chgtime) : 0;
+}
+
+sub get_itemid
+{
+    &LJ::nodb;
+    my ($dmid, $itcode, $opts) = @_;
+    load_lang_struct() unless $LS_CACHED;
+
+    if (length $itcode > MAXIMUM_ITCODE_LENGTH) {
+        warn "'$itcode' exceeds maximum code length, truncating to " . MAXIMUM_ITCODE_LENGTH . " symbols";
+        $itcode = substr($itcode, 0, MAXIMUM_ITCODE_LENGTH);
+    }
+
+    my $dbr = LJ::get_db_reader();
+    $dmid += 0;
+    my $itid = $dbr->selectrow_array("SELECT itid FROM ml_items WHERE dmid=$dmid AND itcode=?", undef, $itcode);
+    return $itid if defined $itid;
+
+    my $dbh = LJ::get_db_writer();
+    return 0 unless $dbh;
+
+    # allocate a new id
+    LJ::get_lock($dbh, 'global', 'mlitem_dmid') || return 0;
+    $itid = $dbh->selectrow_array("SELECT MAX(itid)+1 FROM ml_items WHERE dmid=?", undef, $dmid);
+    $itid ||= 1; # if the table is empty, NULL+1 == NULL
+    $dbh->do("INSERT INTO ml_items (dmid, itid, itcode, notes) ".
+             "VALUES (?, ?, ?, ?)", undef, $dmid, $itid, $itcode, $opts->{'notes'});
+    LJ::release_lock($dbh, 'global', 'mlitem_dmid');
+
+    if ($dbh->err) {
+        return $dbh->selectrow_array("SELECT itid FROM ml_items WHERE dmid=$dmid AND itcode=?",
+                                     undef, $itcode);
+    }
+    return $itid;
+}
+
+# this is called when editing text from a web UI.
+# first try and run a local hook to save the text,
+# if that fails then just call set_text
+
+# returns ($success, $responsemsg) where responsemsg can be output
+# from whatever saves the text
+sub web_set_text {
+    my ($dmid, $lncode, $itcode, $text, $opts) = @_;
+
+    my $resp = '';
+    my $hook_ran = 0;
+
+    if (LJ::are_hooks('web_set_text')) {
+        $hook_ran = LJ::run_hook('web_set_text', $dmid, $lncode, $itcode, $text, $opts);
+    }
+
+    # save in the db
+    my $save_success = LJ::Lang::set_text($dmid, $lncode, $itcode, $text, $opts);
+    $resp = LJ::Lang::last_error() unless $save_success;
+    warn $resp if ! $save_success && $LJ::IS_DEV_SERVER;
+
+    return ($save_success, $resp);
+}
+
+sub set_text
+{
+    &LJ::nodb;
+    my ($dmid, $lncode, $itcode, $text, $opts) = @_;
+    load_lang_struct() unless $LS_CACHED;
+
+    my $l = $LN_CODE{$lncode} or return set_error("Language not defined.");
+    my $lnid = $l->{'lnid'};
+    $dmid += 0;
+
+    # is this domain/language request even possible?
+    return set_error("Bogus domain")
+        unless exists $DM_ID{$dmid};
+    return set_error("Bogus lang for that domain")
+        unless exists $DM_ID{$dmid}->{'langs'}->{$lnid};
+
+    my $itid = get_itemid($dmid, $itcode, { 'notes' => $opts->{'notes'}});
+    return set_error("Couldn't allocate itid.") unless $itid;
+
+    my $dbh = LJ::get_db_writer();
+    my $txtid = 0;
+
+    my $oldtextid = $dbh->selectrow_array("SELECT txtid FROM ml_text WHERE lnid=? AND dmid=? AND itid=?", undef, $lnid, $dmid, $itid);
+
+    if (defined $text) {
+        my $userid = $opts->{'userid'} + 0;
+        # Strip bad characters
+        $text =~ s/\r//;
+        my $qtext = $dbh->quote($text);
+        LJ::get_lock( $dbh, 'global', 'ml_text_txtid' ) || return 0;
+        $txtid = $dbh->selectrow_array("SELECT MAX(txtid)+1 FROM ml_text WHERE dmid=?", undef, $dmid);
+        $txtid ||= 1;
+        $dbh->do("INSERT INTO ml_text (dmid, txtid, lnid, itid, text, userid) ".
+                 "VALUES ($dmid, $txtid, $lnid, $itid, $qtext, $userid)");
+        LJ::release_lock( $dbh, 'global', 'ml_text_txtid' );
+        return set_error("Error inserting ml_text: ".$dbh->errstr) if $dbh->err;
+    }
+    if ($opts->{'txtid'}) {
+        $txtid = $opts->{'txtid'}+0;
+    }
+
+    my $staleness = $opts->{'staleness'}+0;
+    $dbh->do("REPLACE INTO ml_latest (lnid, dmid, itid, txtid, chgtime, staleness) ".
+             "VALUES ($lnid, $dmid, $itid, $txtid, NOW(), $staleness)");
+    return set_error("Error inserting ml_latest: ".$dbh->errstr) if $dbh->err;
+    LJ::MemCache::set("ml.${lncode}.${dmid}.${itcode}", $text) if defined $text;
+
+    my $langids;
+    {
+        my $vals;
+        my $rec = sub {
+            my $l = shift;
+            my $rec = shift;
+            foreach my $cid (@{$l->{'children'}}) {
+                my $clid = $LN_ID{$cid};
+                if ($opts->{'childrenlatest'}) {
+                    my $stale = $clid->{'parenttype'} eq "diff" ? 3 : 0;
+                    $vals .= "," if $vals;
+                    $vals .= "($cid, $dmid, $itid, $txtid, NOW(), $stale)";
+                }
+                $langids .= "," if $langids;
+                $langids .= $cid+0;
+                LJ::MemCache::delete("ml.$clid->{'lncode'}.${dmid}.${itcode}");
+                $rec->($clid, $rec);
+            }
+        };
+        $rec->($l, $rec);
+
+        # set descendants to use this mapping
+        $dbh->do("INSERT IGNORE INTO ml_latest (lnid, dmid, itid, txtid, chgtime, staleness) ".
+                 "VALUES $vals") if $vals;
+
+        # update languages that have no translation yet
+        if ($oldtextid) {
+            $dbh->do("UPDATE ml_latest SET txtid=$txtid WHERE dmid=$dmid ".
+                 "AND lnid IN ($langids) AND itid=$itid AND txtid=$oldtextid") if $langids;
+        } else {
+            $dbh->do("UPDATE ml_latest SET txtid=$txtid WHERE dmid=$dmid ".
+                 "AND lnid IN ($langids) AND itid=$itid AND staleness >= 3") if $langids;
+        }
+    }
+
+    if ($opts->{'changeseverity'} && $langids) {
+        my $newstale = $opts->{'changeseverity'} == 2 ? 2 : 1;
+        $dbh->do("UPDATE ml_latest SET staleness=$newstale WHERE lnid IN ($langids) AND ".
+                 "dmid=$dmid AND itid=$itid AND txtid<>$txtid AND staleness < $newstale");
+    }
+
+    return 1;
+}
+
+sub remove_text {
+    my ($dmid, $itcode, $lncode) = @_;
+
+    my $dbh = LJ::get_db_writer();
+
+    my $itid = $dbh->selectrow_array("SELECT itid FROM ml_items WHERE dmid=? AND itcode=?",
+                                     undef, $dmid, $itcode);
+    die "Unknown item code $itcode." unless $itid;
+
+    # need to delete everything from: ml_items ml_latest ml_text
+
+    $dbh->do("DELETE FROM ml_items WHERE dmid=? AND itid=?",
+             undef, $dmid, $itid);
+
+    my @txtids = ();
+    my $sth = $dbh->prepare("SELECT txtid FROM ml_latest WHERE dmid=? AND itid=?");
+    $sth->execute($dmid, $itid);
+    while (my $txtid = $sth->fetchrow_array) {
+        push @txtids, $txtid;
+    }
+
+    $dbh->do("DELETE FROM ml_latest WHERE dmid=? AND itid=?",
+             undef, $dmid, $itid);
+
+    my $txtid_bind = join(",", map { "?" } @txtids);
+    $dbh->do("DELETE FROM ml_text WHERE dmid=? AND txtid IN ($txtid_bind)",
+             undef, $dmid, @txtids);
+
+    # delete from memcache if lncode is defined
+    LJ::MemCache::delete("ml.${lncode}.${dmid}.${itcode}") if $lncode;
+
+    return 1;
+}
+
+sub get_effective_lang {
+
+    my $lang;
+    if (LJ::is_web_context()) {
+        $lang = BML::get_language();
+    } 
+    if (my $remote = LJ::get_remote()) {
+        # we have a user; try their browse language
+        $lang ||= $remote->prop("browselang");
+    }
+
+    # did we get a valid language code?
+    if ($lang && $LN_CODE{$lang}) {
+        return $lang;
+    }
+
+    # had no language code, or invalid.  return default
+    return $LJ::DEFAULT_LANG;
+}
+
+sub ml {
+    my ($code, $vars) = @_;
+
+    if (LJ::is_web_context()) {
+        # this means we should use BML::ml and not do our own handling
+        my $text = BML::ml($code, $vars);
+        $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
+        return $text;
+    }
+
+    my $lang = LJ::Lang::get_effective_lang();
+    return get_text($lang, $code, undef, $vars);
+}
+
+sub string_exists {
+    my ($code, $vars) = @_;
+
+    my $string = LJ::Lang::ml($code, $vars);
+    return LJ::Lang::is_missing_string($string) ? 0 : 1;
+}
+
+# LJ::Lang::ml will return a number of values for "invalid string"
+# -- this function will tell you if the value is one of
+#    those values.  gross.
+sub is_missing_string {
+    my $string = shift;
+
+    return ( $string eq "" ||
+             $string =~ /^\[missing string/ ||
+             $string =~ /^\[uhhh:/ ) ? 1 : 0;
+}
+
+sub get_text
+{
+    my ($lang, $code, $dmid, $vars) = @_;
+    $lang ||= $LJ::DEFAULT_LANG;
+    
+    my $from_db = sub {
+        my $text = get_text_multi($lang, $dmid, [ $code ]);
+        return $text->{$code};
+    };
+
+    my $from_files = sub {
+        my ($localcode, @files);
+        if ($code =~ m!^(/.+\.bml)(\..+)!) {
+            my $file;
+            ($file, $localcode) = ("$LJ::HTDOCS$1", $2);
+            @files = ("$file.text.local", "$file.text");
+        } else {
+            $localcode = $code;
+            @files = ("$LJ::HOME/bin/upgrading/$LJ::DEFAULT_LANG.dat",
+                      "$LJ::HOME/bin/upgrading/en.dat");
+        }
+
+        foreach my $tf (@files) {
+            next unless -e $tf;
+
+            # compare file modtime to when the string was updated in the DB.
+            # whichever is newer is authoritative
+            my $fmodtime = (stat $tf)[9];
+            my $dbmodtime = LJ::Lang::get_chgtime_unix($lang, $dmid, $code);
+            return $from_db->() if ! $fmodtime || $dbmodtime > $fmodtime;
+
+            my $ldf = $LJ::REQ_LANGDATFILE{$tf} ||= LJ::LangDatFile->new($tf);
+            my $val = $ldf->value($localcode);
+            return $val if $val;
+        }
+        return "[missing string $code]";
+    };
+
+    my $gen_mld = LJ::Lang::get_dom('general');
+    my $is_gen_dmid = defined $dmid ? $dmid == $gen_mld->{dmid} : 1;
+    my $text = ($LJ::IS_DEV_SERVER && $is_gen_dmid &&
+                                      ($lang eq "en" ||
+                                       $lang eq $LJ::DEFAULT_LANG)) ?
+                                       $from_files->() :
+                                       $from_db->();
+
+    if ($vars) {
+        $text =~ s/\[\[\?([\w\-]+)\|(.+?)\]\]/resolve_plural($lang, $vars, $1, $2)/eg;
+        $text =~ s/\[\[([^\[]+?)\]\]/$vars->{$1}/g;
+    }
+
+    $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
+
+    return $text || ($LJ::IS_DEV_SERVER ? "[uhhh: $code]" : "");
+}
+
+# Loads multiple language strings at once.  These strings
+# cannot however contain variables, if you have variables
+# you wouldn't be calling this anyway!
+# args: $lang, $dmid, array ref of lang codes
+sub get_text_multi
+{
+    my ($lang, $dmid, $codes) = @_;
+
+    return {} unless $codes;
+
+    $dmid = int($dmid || 1);
+    $lang ||= $LJ::DEFAULT_LANG;
+    load_lang_struct() unless $LS_CACHED;
+    ## %strings: code --> text
+    my %strings;
+
+    ## normalize the codes: all chars must be in lower case
+    ## MySQL string comparison isn't case-sensitive, but memcaches keys are.
+    ## Caller will get %strings with keys in original case.
+    ##
+    ## Final note about case:  
+    ##  Codes in disk .text files, mysql and bml files may be mixed-cased
+    ##  Codes in memcache and %TXT_CACHE are lower-case
+    ##  Codes are not case-sensitive
+    
+    ## %lc_code: lower-case code --> original code
+    my %lc_codes = map { lc($_) => $_ } @$codes;
+    
+    ## %memkeys: lower-case code --> memcache key
+    my %memkeys; 
+    foreach my $code (keys %lc_codes) {
+        my $cache_key = "ml.${lang}.${dmid}.${code}";
+        my $text = $TXT_CACHE{$cache_key} unless $LJ::NO_ML_CACHE;
+        
+        if (defined $text) {
+            $strings{ $lc_codes{$code} } = $text;
+            $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
+        } else {
+            $memkeys{$cache_key} = $code;
+        }
+    }
+
+    return \%strings unless %memkeys;
+
+    my $mem = LJ::MemCache::get_multi(keys %memkeys) || {};
+
+    ## %dbload: lower-case key --> text; text may be empty (but defined) string
+    my %dbload;
+    foreach my $cache_key (keys %memkeys) {
+        my $code = $memkeys{$cache_key};
+        my $text = $mem->{$cache_key};
+        
+        if (defined $text) {
+            $strings{ $lc_codes{$code} } = $text;
+            $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
+            $TXT_CACHE{$cache_key} = $text;
+        } else {
+            # we need to cache nonexistant/empty strings because otherwise we're running a lot of queries all the time
+            # to cache nonexistant strings, value of %dbload must be defined
+            $dbload{$code} = '';
+        }
+    }
+
+    return \%strings unless %dbload;
+
+    my $l = $LN_CODE{$lang};
+
+    # This shouldn't happen!
+    die ("Unable to load language code: $lang") unless $l;
+
+    my $dbr = LJ::get_db_reader();
+    my $bind = join(',', map { '?' } keys %dbload);
+    my $sth = $dbr->prepare("SELECT i.itcode, t.text, i.visible".
+                            " FROM ml_text t, ml_latest l, ml_items i".
+                            " WHERE t.dmid=? AND t.txtid=l.txtid".
+                            " AND l.dmid=? AND l.lnid=? AND l.itid=i.itid".
+                            " AND i.dmid=? AND i.itcode IN ($bind)");
+    $sth->execute($dmid, $dmid, $l->{lnid}, $dmid, keys %dbload);
+
+    # now replace the empty strings with the defined ones that we got back from the database
+    while (my ($code, $text, $vis) = $sth->fetchrow_array) {
+        # some MySQL codes might be mixed-case
+        $dbload{ lc($code) } = $text;
+
+        # if not currently visible, then set it
+        unless ( $vis ) {
+            my $dbh = LJ::get_db_writer();
+            $dbh->do( 'UPDATE ml_items SET visible = 1 WHERE itcode = ?',
+                       undef, $code );
+        }
+    }
+
+    while (my ($code, $text) = each %dbload) {
+        $strings{ $lc_codes{$code} } = $text;
+        $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
+
+        my $cache_key = "ml.${lang}.${dmid}.${code}";
+        $TXT_CACHE{$cache_key} = $text;
+        LJ::MemCache::set($cache_key, $text);
+    }
+
+    return \%strings;
+}
+
+sub get_lang_names {
+    my @langs = @_;
+    push @langs, @LJ::LANGS unless @langs;
+
+    my @list;
+
+    foreach my $code (@langs) {
+        my $l = LJ::Lang::get_lang($code);
+        next unless $l;
+
+        my $item = "langname.$code";
+        my $namethislang = BML::ml($item);
+        my $namenative = LJ::Lang::get_text($l->{'lncode'}, $item);
+
+        push @list, $code, $namenative;
+    }
+
+    return \@list;
+}
+
+sub set_lang {
+    my $lang = shift;
+
+    my $l = LJ::Lang::get_lang($lang);
+    my $remote = LJ::get_remote();
+
+    # default cookie value to set
+    my $cval = $l->{lncode} . "/" . time();
+
+    # if logged in, change userprop and make cookie expiration
+    # the same as their login expiration
+    if ($remote) {
+        $remote->set_prop("browselang", $l->{lncode});
+
+        if ($remote->{_session}->{exptype} eq 'long') {
+            $cval = [ $cval, $remote->{_session}->{timeexpire} ];
+        }
+    }
+
+    # set cookie
+    $BML::COOKIE{langpref} = $cval;
+
+    # set language through BML so it will apply immediately
+    BML::set_language($l->{lncode});
+
+    return;
+}
+
+# The translation system now supports the ability to add multiple plural forms of the word
+# given different rules in a languge.  This functionality is much like the plural support
+# in the S2 styles code.  To use this code you must use the BML::ml function and pass
+# the number of items as one of the variables.  To make sure that you are allowing the
+# utmost compatibility for each language you should not hardcode the placement of the
+# number of items in relation to the noun.  Let the translation string do this for you.
+# A translation string is in the format of, with num being the variable storing the
+# number of items.
+# =[[num]] [[?num|singular|plural1|plural2|pluralx]]
+
+sub resolve_plural {
+    my ($lang, $vars, $varname, $wordlist) = @_;
+    my $count = $vars->{$varname};
+    my @wlist = split(/\|/, $wordlist);
+    my $plural_form = plural_form($lang, $count);
+    return $wlist[$plural_form];
+}
+
+# TODO: make this faster, using AUTOLOAD and symbol tables pointing to dynamically
+# generated subs which only use $_[0] for $count.
+sub plural_form {
+    my ($lang, $count) = @_;
+    return plural_form_en($count) if $lang =~ /^en/;
+    return plural_form_ru($count) if $lang =~ /^ru/ || $lang =~ /^uk/ || $lang =~ /^be/;
+    return plural_form_fr($count) if $lang =~ /^fr/ || $lang =~ /^pt_BR/;
+    return plural_form_lt($count) if $lang =~ /^lt/;
+    return plural_form_pl($count) if $lang =~ /^pl/;
+    return plural_form_singular() if $lang =~ /^hu/ || $lang =~ /^ja/ || $lang =~ /^tr/;
+    return plural_form_lv($count) if $lang =~ /^lv/;
+    return plural_form_is($count) if $lang =~ /^is/;
+    return plural_form_en($count);  # default
+}
+
+# English, Danish, German, Norwegian, Swedish, Estonian, Finnish, Greek, Hebrew, Italian, Portugese, Spanish, Esperanto
+sub plural_form_en {
+    my ($count) = shift;
+    return 0 if $count == 1;
+    return 1;
+}
+
+# French, Brazilian Portuguese
+sub plural_form_fr {
+    my ($count) = shift;
+    return 1 if $count > 1;
+    return 0;
+}
+
+# Croatian, Czech, Russian, Slovak, Ukrainian, Belarusian
+sub plural_form_ru {
+    my ($count) = shift;
+    return 0 if ($count%10 == 1 and $count%100 != 11);
+    return 1 if ($count%10 >= 2 and $count%10 <= 4 and ($count%100 < 10 or $count%100>=20));
+    return 2;
+}
+
+# Polish
+sub plural_form_pl {
+    my ($count) = shift;
+    return 0 if($count == 1);
+    return 1 if($count%10 >= 2 && $count%10 <= 4 && ($count%100 < 10 || $count%100 >= 20));
+    return 2;
+}
+
+# Lithuanian
+sub plural_form_lt {
+    my ($count) = shift;
+    return 0 if($count%10 == 1 && $count%100 != 11);
+    return 1 if ($count%10 >= 2 && ($count%100 < 10 || $count%100 >= 20));
+    return 2;
+}
+
+# Hungarian, Japanese, Korean (not supported), Turkish
+sub plural_form_singular {
+    return 0;
+}
+
+# Latvian
+sub plural_form_lv {
+    my ($count) = shift;
+    return 0 if($count%10 == 1 && $count%100 != 11);
+    return 1 if($count != 0);
+    return 2;
+}
+
+# Icelandic
+sub plural_form_is {
+    my ($count) = shift;
+    return 0 if ($count%10 == 1 and $count%100 != 11);
+    return 1;
+}
+
+1;
diff -r 0b01e0370322 -r e8d5bfb23489 cgi-bin/ljlang.pl
--- a/cgi-bin/ljlang.pl	Sat Sep 12 12:14:17 2009 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,840 +0,0 @@
-#!/usr/bin/perl
-#
-
-use strict;
-use lib "$LJ::HOME/cgi-bin";
-
-use Class::Autouse qw(
-                      LJ::LangDatFile
-                      );
-
-package LJ::Lang;
-
-use constant MAXIMUM_ITCODE_LENGTH => 80;
-
-my @day_short   = (qw[Sun Mon Tue Wed Thu Fri Sat]);
-my @day_long    = (qw[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]);
-my @month_short = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]);
-my @month_long  = (qw[January February March April May June July August September October November December]);
-
-# For init_cvsprefixes(), langdat_file_of_lang_itcode(), and stuff.
-my @shared_cvsprefixes = qw(cvs/livejournal cvs/dw-free);
-my @local_cvsprefixes = qw(cvs/local cvs/dw-nonfree);
-my ($cvspfx_shared, $cvspfx_local);
-
-# get entire array of days and months
-sub day_list_short   { return @LJ::Lang::day_short;   }
-sub day_list_long    { return @LJ::Lang::day_long;    }
-sub month_list_short { return @LJ::Lang::month_short; }
-sub month_list_long  { return @LJ::Lang::month_long;  }
-
-# access individual day or month given integer
-sub day_short   { return   $day_short[$_[0] - 1]; }
-sub day_long    { return    $day_long[$_[0] - 1]; }
-sub month_short { return $month_short[$_[0] - 1]; }
-sub month_long  { return  $month_long[$_[0] - 1]; }
-
-# lang codes for individual day or month given integer
-sub day_short_langcode   { return "date.day."   . lc(LJ::Lang::day_long(@_))    . ".short"; }
-sub day_long_langcode    { return "date.day."   . lc(LJ::Lang::day_long(@_))    . ".long";  }
-sub month_short_langcode { return "date.month." . lc(LJ::Lang::month_long(@_))  . ".short"; }
-sub month_long_langcode  { return "date.month." . lc(LJ::Lang::month_long(@_))  . ".long";  }
-
-# Translated names for individual day or month given integer. You probably want
-# these, not the ones above.
-sub day_short_ml { return LJ::Lang::ml( LJ::Lang::day_short_langcode( @_ ) ); }
-sub day_long_ml { return LJ::Lang::ml( LJ::Lang::day_long_langcode( @_ ) ); }
-sub month_short_ml { return LJ::Lang::ml( LJ::Lang::month_short_langcode( @_ ) ); }
-sub month_long_ml { return LJ::Lang::ml( LJ::Lang::month_long_langcode( @_ ) ); }
-
-## ordinal suffix
-sub day_ord {
-    my $day = shift;
-
-    # teens all end in 'th'
-    if ($day =~ /1\d$/) { return "th"; }
-
-    # otherwise endings in 1, 2, 3 are special
-    if ($day % 10 == 1) { return "st"; }
-    if ($day % 10 == 2) { return "nd"; }
-    if ($day % 10 == 3) { return "rd"; }
-
-    # everything else (0,4-9) end in "th"
-    return "th";
-}
-
-sub time_format
-{
-    my ($hours, $h, $m, $formatstring) = @_;
-
-    if ($formatstring eq "short") {
-        if ($hours == 12) {
-            my $ret;
-            my $ap = "a";
-            if ($h == 0) { $ret .= "12"; }
-            elsif ($h < 12) { $ret .= ($h+0); }
-            elsif ($h == 12) { $ret .= ($h+0); $ap = "p"; }
-            else { $ret .= ($h-12); $ap = "p"; }
-            $ret .= sprintf(":%02d$ap", $m);
-            return $ret;
-        } elsif ($hours == 24) {
-            return sprintf("%02d:%02d", $h, $m);
-        }
-    }
-    return "";
-}
-
-#### ml_ stuff:
-my $LS_CACHED = 0;
-my %DM_ID = ();     # id -> { type, args, dmid, langs => { => 1, => 0, => 1 } }
-my %DM_UNIQ = ();   # "$type/$args" => ^^^
-my %LN_ID = ();     # id -> { ..., ..., 'children' => [ $ids, .. ] }
-my %LN_CODE = ();   # $code -> ^^^^
-my $LAST_ERROR;
-my %TXT_CACHE;
-
-sub last_error
-{
-    return $LAST_ERROR;
-}
-
-sub set_error
-{
-    $LAST_ERROR = $_[0];
-    return 0;
-}
-
-sub get_lang
-{
-    my $code = shift;
-    load_lang_struct() unless $LS_CACHED;
-    return $LN_CODE{$code};
-}
-
-sub get_lang_id
-{
-    my $id = shift;
-    load_lang_struct() unless $LS_CACHED;
-    return $LN_ID{$id};
-}
-
-sub get_dom
-{
-    my $dmcode = shift;
-    load_lang_struct() unless $LS_CACHED;
-    return $DM_UNIQ{$dmcode};
-}
-
-sub get_dom_id
-{
-    my $dmid = shift;
-    load_lang_struct() unless $LS_CACHED;
-    return $DM_ID{$dmid};
-}
-
-sub get_domains
-{
-    load_lang_struct() unless $LS_CACHED;
-    return values %DM_ID;
-}
-
-sub get_root_lang
-{
-    my $dom = shift;  # from, say, get_dom
-    return undef unless ref $dom eq "HASH";
-
-    my $lang_override = LJ::run_hook("root_lang_override", $dom);
-    return get_lang($lang_override) if $lang_override;
-
-    foreach (keys %{$dom->{'langs'}}) {
-        if ($dom->{'langs'}->{$_}) {
-            return get_lang_id($_);
-        }
-    }
-    return undef;
-}
-
-sub load_lang_struct
-{
-    return 1 if $LS_CACHED;
-    my $dbr = LJ::get_db_reader();
-    return set_error("No database available") unless $dbr;
-    my $sth;
-
-    $sth = $dbr->prepare("SELECT dmid, type, args FROM ml_domains");
-    $sth->execute;
-    while (my ($dmid, $type, $args) = $sth->fetchrow_array) {
-        my $uniq = $args ? "$type/$args" : $type;
-        $DM_UNIQ{$uniq} = $DM_ID{$dmid} = {
-            'type' => $type, 'args' => $args, 'dmid' => $dmid,
-            'uniq' => $uniq,
-        };
-    }
-
-    $sth = $dbr->prepare("SELECT lnid, lncode, lnname, parenttype, parentlnid FROM ml_langs");
-    $sth->execute;
-    while (my ($id, $code, $name, $ptype, $pid) = $sth->fetchrow_array) {
-        $LN_ID{$id} = $LN_CODE{$code} = {
-            'lnid' => $id,
-            'lncode' => $code,
-            'lnname' => $name,
-            'parenttype' => $ptype,
-            'parentlnid' => $pid,
-        };
-    }
-    foreach (values %LN_CODE) {
-        next unless $_->{'parentlnid'};
-        push @{$LN_ID{$_->{'parentlnid'}}->{'children'}}, $_->{'lnid'};
-    }
-
-    $sth = $dbr->prepare("SELECT lnid, dmid, dmmaster FROM ml_langdomains");
-    $sth->execute;
-    while (my ($lnid, $dmid, $dmmaster) = $sth->fetchrow_array) {
-        $DM_ID{$dmid}->{'langs'}->{$lnid} = $dmmaster;
-    }
-
-    $LS_CACHED = 1;
-}
-
-sub init_cvsprefixes {
-    return if defined($cvspfx_shared);
-
-    foreach my $p (@shared_cvsprefixes) {
-        if (-d "$LJ::HOME/$p") {
-            $cvspfx_shared = $p;
-            last;
-        }
-    }
-    $cvspfx_shared ||= "";
-
-    foreach my $p (@local_cvsprefixes) {
-        if (-d "$LJ::HOME/$p") {
-            $cvspfx_local = $p;
-            last;
-        }
-    }
-    $cvspfx_local ||= "";
-}
-
-sub langdat_file_of_lang_itcode
-{
-    my ($lang, $itcode, $want_cvs) = @_;
-
-    my $langdat_file = LJ::Lang::relative_langdat_file_of_lang_itcode($lang, $itcode);
-    my $cvs_extra = "";
-    if ($want_cvs) {
-        init_cvsprefixes();
-        if ($lang eq "en") {
-            $cvs_extra = "/$cvspfx_shared";
-        } else {
-            $cvs_extra = "/$cvspfx_local";
-        }
-    }
-    return "$LJ::HOME$cvs_extra/$langdat_file";
-}
-
-sub relative_langdat_file_of_lang_itcode
-{
-    my ($lang, $itcode) = @_;
-
-    my $root_lang = "en";
-    my $root_lang_local = $LJ::DEFAULT_LANG;
-
-    my $base_file = "bin/upgrading/$lang\.dat";
-
-    # not a root or root_local lang, just return base file location
-    unless ($lang eq $root_lang || $lang eq $root_lang_local) {
-        return $base_file;
-    }
-
-    my $is_local = $lang eq $root_lang_local;
-
-    # is this a filename-based itcode?
-    if ($itcode =~ m!^(/.+\.bml)!) {
-        my $file = $1;
-
-        # given the filename of this itcode and the current
-        # source, what langdat file should we use?
-        my $langdat_file = "htdocs$file\.text";
-        $langdat_file .= $is_local ? ".local" : "";
-        return $langdat_file;
-    }
-
-    # not a bml file, goes into base .dat file
-    return $base_file;
-}
-
-sub itcode_for_langdat_file {
-    my ($langdat_file, $itcode) = @_;
-
-    # non-bml itcode, return full itcode path
-    unless ($langdat_file =~ m!^/.+\.bml\.text(?:\.local)?$!) {
-        return $itcode;
-    }
-
-    # bml itcode, strip filename and return
-    if ($itcode =~ m!^/.+\.bml(\..+)!) {
-        return $1;
-    }
-
-    # fallback -- full $itcode
-    return $itcode;
-}
-
-sub get_chgtime_unix
-{
-    my ($lncode, $dmid, $itcode) = @_;
-    load_lang_struct() unless $LS_CACHED;
-
-    $dmid = int($dmid || 1);
-
-    my $l = get_lang($lncode) or return "No lang info for lang $lncode";
-    my $lnid = $l->{'lnid'}
-        or die "Could not get lang_id for lang $lncode";
-
-    my $itid = LJ::Lang::get_itemid($dmid, $itcode)
-        or return 0;
-
-    my $dbr = LJ::get_db_reader();
-    $dmid += 0;
-    my $chgtime = $dbr->selectrow_array("SELECT chgtime FROM ml_latest WHERE dmid=? AND itid=? AND lnid=?",
-                                        undef, $dmid, $itid, $lnid);
-    die $dbr->errstr if $dbr->err;
-    return $chgtime ? LJ::mysqldate_to_time($chgtime) : 0;
-}
-
-sub get_itemid
-{
-    &LJ::nodb;
-    my ($dmid, $itcode, $opts) = @_;
-    load_lang_struct() unless $LS_CACHED;
-
-    if (length $itcode > MAXIMUM_ITCODE_LENGTH) {
-        warn "'$itcode' exceeds maximum code length, truncating to " . MAXIMUM_ITCODE_LENGTH . " symbols";
-        $itcode = substr($itcode, 0, MAXIMUM_ITCODE_LENGTH);
-    }
-
-    my $dbr = LJ::get_db_reader();
-    $dmid += 0;
-    my $itid = $dbr->selectrow_array("SELECT itid FROM ml_items WHERE dmid=$dmid AND itcode=?", undef, $itcode);
-    return $itid if defined $itid;
-
-    my $dbh = LJ::get_db_writer();
-    return 0 unless $dbh;
-
-    # allocate a new id
-    LJ::get_lock($dbh, 'global', 'mlitem_dmid') || return 0;
-    $itid = $dbh->selectrow_array("SELECT MAX(itid)+1 FROM ml_items WHERE dmid=?", undef, $dmid);
-    $itid ||= 1; # if the table is empty, NULL+1 == NULL
-    $dbh->do("INSERT INTO ml_items (dmid, itid, itcode, notes) ".
-             "VALUES (?, ?, ?, ?)", undef, $dmid, $itid, $itcode, $opts->{'notes'});
-    LJ::release_lock($dbh, 'global', 'mlitem_dmid');
-
-    if ($dbh->err) {
-        return $dbh->selectrow_array("SELECT itid FROM ml_items WHERE dmid=$dmid AND itcode=?",
-                                     undef, $itcode);
-    }
-    return $itid;
-}
-
-# this is called when editing text from a web UI.
-# first try and run a local hook to save the text,
-# if that fails then just call set_text
-
-# returns ($success, $responsemsg) where responsemsg can be output
-# from whatever saves the text
-sub web_set_text {
-    my ($dmid, $lncode, $itcode, $text, $opts) = @_;
-
-    my $resp = '';
-    my $hook_ran = 0;
-
-    if (LJ::are_hooks('web_set_text')) {
-        $hook_ran = LJ::run_hook('web_set_text', $dmid, $lncode, $itcode, $text, $opts);
-    }
-
-    # save in the db
-    my $save_success = LJ::Lang::set_text($dmid, $lncode, $itcode, $text, $opts);
-    $resp = LJ::Lang::last_error() unless $save_success;
-    warn $resp if ! $save_success && $LJ::IS_DEV_SERVER;
-
-    return ($save_success, $resp);
-}
-
-sub set_text
-{
-    &LJ::nodb;
-    my ($dmid, $lncode, $itcode, $text, $opts) = @_;
-    load_lang_struct() unless $LS_CACHED;
-
-    my $l = $LN_CODE{$lncode} or return set_error("Language not defined.");
-    my $lnid = $l->{'lnid'};
-    $dmid += 0;
-
-    # is this domain/language request even possible?
-    return set_error("Bogus domain")
-        unless exists $DM_ID{$dmid};
-    return set_error("Bogus lang for that domain")
-        unless exists $DM_ID{$dmid}->{'langs'}->{$lnid};
-
-    my $itid = get_itemid($dmid, $itcode, { 'notes' => $opts->{'notes'}});
-    return set_error("Couldn't allocate itid.") unless $itid;
-
-    my $dbh = LJ::get_db_writer();
-    my $txtid = 0;
-
-    my $oldtextid = $dbh->selectrow_array("SELECT txtid FROM ml_text WHERE lnid=? AND dmid=? AND itid=?", undef, $lnid, $dmid, $itid);
-
-    if (defined $text) {
-        my $userid = $opts->{'userid'} + 0;
-        # Strip bad characters
-        $text =~ s/\r//;
-        my $qtext = $dbh->quote($text);
-        LJ::get_lock( $dbh, 'global', 'ml_text_txtid' ) || return 0;
-        $txtid = $dbh->selectrow_array("SELECT MAX(txtid)+1 FROM ml_text WHERE dmid=?", undef, $dmid);
-        $txtid ||= 1;
-        $dbh->do("INSERT INTO ml_text (dmid, txtid, lnid, itid, text, userid) ".
-                 "VALUES ($dmid, $txtid, $lnid, $itid, $qtext, $userid)");
-        LJ::release_lock( $dbh, 'global', 'ml_text_txtid' );
-        return set_error("Error inserting ml_text: ".$dbh->errstr) if $dbh->err;
-    }
-    if ($opts->{'txtid'}) {
-        $txtid = $opts->{'txtid'}+0;
-    }
-
-    my $staleness = $opts->{'staleness'}+0;
-    $dbh->do("REPLACE INTO ml_latest (lnid, dmid, itid, txtid, chgtime, staleness) ".
-             "VALUES ($lnid, $dmid, $itid, $txtid, NOW(), $staleness)");
-    return set_error("Error inserting ml_latest: ".$dbh->errstr) if $dbh->err;
-    LJ::MemCache::set("ml.${lncode}.${dmid}.${itcode}", $text) if defined $text;
-
-    my $langids;
-    {
-        my $vals;
-        my $rec = sub {
-            my $l = shift;
-            my $rec = shift;
-            foreach my $cid (@{$l->{'children'}}) {
-                my $clid = $LN_ID{$cid};
-                if ($opts->{'childrenlatest'}) {
-                    my $stale = $clid->{'parenttype'} eq "diff" ? 3 : 0;
-                    $vals .= "," if $vals;
-                    $vals .= "($cid, $dmid, $itid, $txtid, NOW(), $stale)";
-                }
-                $langids .= "," if $langids;
-                $langids .= $cid+0;
-                LJ::MemCache::delete("ml.$clid->{'lncode'}.${dmid}.${itcode}");
-                $rec->($clid, $rec);
-            }
-        };
-        $rec->($l, $rec);
-
-        # set descendants to use this mapping
-        $dbh->do("INSERT IGNORE INTO ml_latest (lnid, dmid, itid, txtid, chgtime, staleness) ".
-                 "VALUES $vals") if $vals;
-
-        # update languages that have no translation yet
-        if ($oldtextid) {
-            $dbh->do("UPDATE ml_latest SET txtid=$txtid WHERE dmid=$dmid ".
-                 "AND lnid IN ($langids) AND itid=$itid AND txtid=$oldtextid") if $langids;
-        } else {
-            $dbh->do("UPDATE ml_latest SET txtid=$txtid WHERE dmid=$dmid ".
-                 "AND lnid IN ($langids) AND itid=$itid AND staleness >= 3") if $langids;
-        }
-    }
-
-    if ($opts->{'changeseverity'} && $langids) {
-        my $newstale = $opts->{'changeseverity'} == 2 ? 2 : 1;
-        $dbh->do("UPDATE ml_latest SET staleness=$newstale WHERE lnid IN ($langids) AND ".
-                 "dmid=$dmid AND itid=$itid AND txtid<>$txtid AND staleness < $newstale");
-    }
-
-    return 1;
-}
-
-sub remove_text {
-    my ($dmid, $itcode, $lncode) = @_;
-
-    my $dbh = LJ::get_db_writer();
-
-    my $itid = $dbh->selectrow_array("SELECT itid FROM ml_items WHERE dmid=? AND itcode=?",
-                                     undef, $dmid, $itcode);
-    die "Unknown item code $itcode." unless $itid;
-
-    # need to delete everything from: ml_items ml_latest ml_text
-
-    $dbh->do("DELETE FROM ml_items WHERE dmid=? AND itid=?",
-             undef, $dmid, $itid);
-
-    my @txtids = ();
-    my $sth = $dbh->prepare("SELECT txtid FROM ml_latest WHERE dmid=? AND itid=?");
-    $sth->execute($dmid, $itid);
-    while (my $txtid = $sth->fetchrow_array) {
-        push @txtids, $txtid;
-    }
-
-    $dbh->do("DELETE FROM ml_latest WHERE dmid=? AND itid=?",
-             undef, $dmid, $itid);
-
-    my $txtid_bind = join(",", map { "?" } @txtids);
-    $dbh->do("DELETE FROM ml_text WHERE dmid=? AND txtid IN ($txtid_bind)",
-             undef, $dmid, @txtids);
-
-    # delete from memcache if lncode is defined
-    LJ::MemCache::delete("ml.${lncode}.${dmid}.${itcode}") if $lncode;
-
-    return 1;
-}
-
-sub get_effective_lang {
-
-    my $lang;
-    if (LJ::is_web_context()) {
-        $lang = BML::get_language();
-    } 
-    if (my $remote = LJ::get_remote()) {
-        # we have a user; try their browse language
-        $lang ||= $remote->prop("browselang");
-    }
-
-    # did we get a valid language code?
-    if ($lang && $LN_CODE{$lang}) {
-        return $lang;
-    }
-
-    # had no language code, or invalid.  return default
-    return $LJ::DEFAULT_LANG;
-}
-
-sub ml {
-    my ($code, $vars) = @_;
-
-    if (LJ::is_web_context()) {
-        # this means we should use BML::ml and not do our own handling
-        my $text = BML::ml($code, $vars);
-        $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
-        return $text;
-    }
-
-    my $lang = LJ::Lang::get_effective_lang();
-    return get_text($lang, $code, undef, $vars);
-}
-
-sub string_exists {
-    my ($code, $vars) = @_;
-
-    my $string = LJ::Lang::ml($code, $vars);
-    return LJ::Lang::is_missing_string($string) ? 0 : 1;
-}
-
-# LJ::Lang::ml will return a number of values for "invalid string"
-# -- this function will tell you if the value is one of
-#    those values.  gross.
-sub is_missing_string {
-    my $string = shift;
-
-    return ( $string eq "" ||
-             $string =~ /^\[missing string/ ||
-             $string =~ /^\[uhhh:/ ) ? 1 : 0;
-}
-
-sub get_text
-{
-    my ($lang, $code, $dmid, $vars) = @_;
-    $lang ||= $LJ::DEFAULT_LANG;
-    
-    my $from_db = sub {
-        my $text = get_text_multi($lang, $dmid, [ $code ]);
-        return $text->{$code};
-    };
-
-    my $from_files = sub {
-        my ($localcode, @files);
-        if ($code =~ m!^(/.+\.bml)(\..+)!) {
-            my $file;
-            ($file, $localcode) = ("$LJ::HTDOCS$1", $2);
-            @files = ("$file.text.local", "$file.text");
-        } else {
-            $localcode = $code;
-            @files = ("$LJ::HOME/bin/upgrading/$LJ::DEFAULT_LANG.dat",
-                      "$LJ::HOME/bin/upgrading/en.dat");
-        }
-
-        foreach my $tf (@files) {
-            next unless -e $tf;
-
-            # compare file modtime to when the string was updated in the DB.
-            # whichever is newer is authoritative
-            my $fmodtime = (stat $tf)[9];
-            my $dbmodtime = LJ::Lang::get_chgtime_unix($lang, $dmid, $code);
-            return $from_db->() if ! $fmodtime || $dbmodtime > $fmodtime;
-
-            my $ldf = $LJ::REQ_LANGDATFILE{$tf} ||= LJ::LangDatFile->new($tf);
-            my $val = $ldf->value($localcode);
-            return $val if $val;
-        }
-        return "[missing string $code]";
-    };
-
-    my $gen_mld = LJ::Lang::get_dom('general');
-    my $is_gen_dmid = defined $dmid ? $dmid == $gen_mld->{dmid} : 1;
-    my $text = ($LJ::IS_DEV_SERVER && $is_gen_dmid &&
-                                      ($lang eq "en" ||
-                                       $lang eq $LJ::DEFAULT_LANG)) ?
-                                       $from_files->() :
-                                       $from_db->();
-
-    if ($vars) {
-        $text =~ s/\[\[\?([\w\-]+)\|(.+?)\]\]/resolve_plural($lang, $vars, $1, $2)/eg;
-        $text =~ s/\[\[([^\[]+?)\]\]/$vars->{$1}/g;
-    }
-
-    $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
-
-    return $text || ($LJ::IS_DEV_SERVER ? "[uhhh: $code]" : "");
-}
-
-# Loads multiple language strings at once.  These strings
-# cannot however contain variables, if you have variables
-# you wouldn't be calling this anyway!
-# args: $lang, $dmid, array ref of lang codes
-sub get_text_multi
-{
-    my ($lang, $dmid, $codes) = @_;
-
-    return {} unless $codes;
-
-    $dmid = int($dmid || 1);
-    $lang ||= $LJ::DEFAULT_LANG;
-    load_lang_struct() unless $LS_CACHED;
-    ## %strings: code --> text
-    my %strings;
-
-    ## normalize the codes: all chars must be in lower case
-    ## MySQL string comparison isn't case-sensitive, but memcaches keys are.
-    ## Caller will get %strings with keys in original case.
-    ##
-    ## Final note about case:  
-    ##  Codes in disk .text files, mysql and bml files may be mixed-cased
-    ##  Codes in memcache and %TXT_CACHE are lower-case
-    ##  Codes are not case-sensitive
-    
-    ## %lc_code: lower-case code --> original code
-    my %lc_codes = map { lc($_) => $_ } @$codes;
-    
-    ## %memkeys: lower-case code --> memcache key
-    my %memkeys; 
-    foreach my $code (keys %lc_codes) {
-        my $cache_key = "ml.${lang}.${dmid}.${code}";
-        my $text = $TXT_CACHE{$cache_key} unless $LJ::NO_ML_CACHE;
-        
-        if (defined $text) {
-            $strings{ $lc_codes{$code} } = $text;
-            $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
-        } else {
-            $memkeys{$cache_key} = $code;
-        }
-    }
-
-    return \%strings unless %memkeys;
-
-    my $mem = LJ::MemCache::get_multi(keys %memkeys) || {};
-
-    ## %dbload: lower-case key --> text; text may be empty (but defined) string
-    my %dbload;
-    foreach my $cache_key (keys %memkeys) {
-        my $code = $memkeys{$cache_key};
-        my $text = $mem->{$cache_key};
-        
-        if (defined $text) {
-            $strings{ $lc_codes{$code} } = $text;
-            $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
-            $TXT_CACHE{$cache_key} = $text;
-        } else {
-            # we need to cache nonexistant/empty strings because otherwise we're running a lot of queries all the time
-            # to cache nonexistant strings, value of %dbload must be defined
-            $dbload{$code} = '';
-        }
-    }
-
-    return \%strings unless %dbload;
-
-    my $l = $LN_CODE{$lang};
-
-    # This shouldn't happen!
-    die ("Unable to load language code: $lang") unless $l;
-
-    my $dbr = LJ::get_db_reader();
-    my $bind = join(',', map { '?' } keys %dbload);
-    my $sth = $dbr->prepare("SELECT i.itcode, t.text, i.visible".
-                            " FROM ml_text t, ml_latest l, ml_items i".
-                            " WHERE t.dmid=? AND t.txtid=l.txtid".
-                            " AND l.dmid=? AND l.lnid=? AND l.itid=i.itid".
-                            " AND i.dmid=? AND i.itcode IN ($bind)");
-    $sth->execute($dmid, $dmid, $l->{lnid}, $dmid, keys %dbload);
-
-    # now replace the empty strings with the defined ones that we got back from the database
-    while (my ($code, $text, $vis) = $sth->fetchrow_array) {
-        # some MySQL codes might be mixed-case
-        $dbload{ lc($code) } = $text;
-
-        # if not currently visible, then set it
-        unless ( $vis ) {
-            my $dbh = LJ::get_db_writer();
-            $dbh->do( 'UPDATE ml_items SET visible = 1 WHERE itcode = ?',
-                       undef, $code );
-        }
-    }
-
-    while (my ($code, $text) = each %dbload) {
-        $strings{ $lc_codes{$code} } = $text;
-        $LJ::_ML_USED_STRINGS{$code} = $text if $LJ::IS_DEV_SERVER;
-
-        my $cache_key = "ml.${lang}.${dmid}.${code}";
-        $TXT_CACHE{$cache_key} = $text;
-        LJ::MemCache::set($cache_key, $text);
-    }
-
-    return \%strings;
-}
-
-sub get_lang_names {
-    my @langs = @_;
-    push @langs, @LJ::LANGS unless @langs;
-
-    my @list;
-
-    foreach my $code (@langs) {
-        my $l = LJ::Lang::get_lang($code);
-        next unless $l;
-
-        my $item = "langname.$code";
-        my $namethislang = BML::ml($item);
-        my $namenative = LJ::Lang::get_text($l->{'lncode'}, $item);
-
-        push @list, $code, $namenative;
-    }
-
-    return \@list;
-}
-
-sub set_lang {
-    my $lang = shift;
-
-    my $l = LJ::Lang::get_lang($lang);
-    my $remote = LJ::get_remote();
-
-    # default cookie value to set
-    my $cval = $l->{lncode} . "/" . time();
-
-    # if logged in, change userprop and make cookie expiration
-    # the same as their login expiration
-    if ($remote) {
-        $remote->set_prop("browselang", $l->{lncode});
-
-        if ($remote->{_session}->{exptype} eq 'long') {
-            $cval = [ $cval, $remote->{_session}->{timeexpire} ];
-        }
-    }
-
-    # set cookie
-    $BML::COOKIE{langpref} = $cval;
-
-    # set language through BML so it will apply immediately
-    BML::set_language($l->{lncode});
-
-    return;
-}
-
-# The translation system now supports the ability to add multiple plural forms of the word
-# given different rules in a languge.  This functionality is much like the plural support
-# in the S2 styles code.  To use this code you must use the BML::ml function and pass
-# the number of items as one of the variables.  To make sure that you are allowing the
-# utmost compatibility for each language you should not hardcode the placement of the
-# number of items in relation to the noun.  Let the translation string do this for you.
-# A translation string is in the format of, with num being the variable storing the
-# number of items.
-# =[[num]] [[?num|singular|plural1|plural2|pluralx]]
-
-sub resolve_plural {
-    my ($lang, $vars, $varname, $wordlist) = @_;
-    my $count = $vars->{$varname};
-    my @wlist = split(/\|/, $wordlist);
-    my $plural_form = plural_form($lang, $count);
-    return $wlist[$plural_form];
-}
-
-# TODO: make this faster, using AUTOLOAD and symbol tables pointing to dynamically
-# generated subs which only use $_[0] for $count.
-sub plural_form {
-    my ($lang, $count) = @_;
-    return plural_form_en($count) if $lang =~ /^en/;
-    return plural_form_ru($count) if $lang =~ /^ru/ || $lang =~ /^uk/ || $lang =~ /^be/;
-    return plural_form_fr($count) if $lang =~ /^fr/ || $lang =~ /^pt_BR/;
-    return plural_form_lt($count) if $lang =~ /^lt/;
-    return plural_form_pl($count) if $lang =~ /^pl/;
-    return plural_form_singular() if $lang =~ /^hu/ || $lang =~ /^ja/ || $lang =~ /^tr/;
-    return plural_form_lv($count) if $lang =~ /^lv/;
-    return plural_form_is($count) if $lang =~ /^is/;
-    return plural_form_en($count);  # default
-}
-
-# English, Danish, German, Norwegian, Swedish, Estonian, Finnish, Greek, Hebrew, Italian, Portugese, Spanish, Esperanto
-sub plural_form_en {
-    my ($count) = shift;
-    return 0 if $count == 1;
-    return 1;
-}
-
-# French, Brazilian Portuguese
-sub plural_form_fr {
-    my ($count) = shift;
-    return 1 if $count > 1;
-    return 0;
-}
-
-# Croatian, Czech, Russian, Slovak, Ukrainian, Belarusian
-sub plural_form_ru {
-    my ($count) = shift;
-    return 0 if ($count%10 == 1 and $count%100 != 11);
-    return 1 if ($count%10 >= 2 and $count%10 <= 4 and ($count%100 < 10 or $count%100>=20));
-    return 2;
-}
-
-# Polish
-sub plural_form_pl {
-    my ($count) = shift;
-    return 0 if($count == 1);
-    return 1 if($count%10 >= 2 && $count%10 <= 4 && ($count%100 < 10 || $count%100 >= 20));
-    return 2;
-}
-
-# Lithuanian
-sub plural_form_lt {
-    my ($count) = shift;
-    return 0 if($count%10 == 1 && $count%100 != 11);
-    return 1 if ($count%10 >= 2 && ($count%100 < 10 || $count%100 >= 20));
-    return 2;
-}
-
-# Hungarian, Japanese, Korean (not supported), Turkish
-sub plural_form_singular {
-    return 0;
-}
-
-# Latvian
-sub plural_form_lv {
-    my ($count) = shift;
-    return 0 if($count%10 == 1 && $count%100 != 11);
-    return 1 if($count != 0);
-    return 2;
-}
-
-# Icelandic
-sub plural_form_is {
-    my ($count) = shift;
-    return 0 if ($count%10 == 1 and $count%100 != 11);
-    return 1;
-}
-
-1;
diff -r 0b01e0370322 -r e8d5bfb23489 cgi-bin/modperl_subs.pl
--- a/cgi-bin/modperl_subs.pl	Sat Sep 12 12:14:17 2009 +0800
+++ b/cgi-bin/modperl_subs.pl	Sat Sep 12 05:33:57 2009 +0000
@@ -62,7 +62,7 @@ use MIME::Words;
 # Try to load DBI::Profile
 BEGIN { $LJ::HAVE_DBI_PROFILE = eval "use DBI::Profile (); 1;" }
 
-require "ljlang.pl";
+use LJ::Lang;
 require "htmlcontrols.pl";
 require "weblib.pl";
 require "imageconf.pl";
diff -r 0b01e0370322 -r e8d5bfb23489 t/commafy.t
--- a/t/commafy.t	Sat Sep 12 12:14:17 2009 +0800
+++ b/t/commafy.t	Sat Sep 12 05:33:57 2009 +0000
@@ -4,7 +4,7 @@ use Test::More 'no_plan';
 use Test::More 'no_plan';
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 
 is(LJ::commafy("lalala"), "lalala");
 is(LJ::commafy("1"), "1");
diff -r 0b01e0370322 -r e8d5bfb23489 t/console-faqcat.t
--- a/t/console-faqcat.t	Sat Sep 12 12:14:17 2009 +0800
+++ b/t/console-faqcat.t	Sat Sep 12 05:33:57 2009 +0000
@@ -3,8 +3,8 @@ use Test::More tests => 15;
 use Test::More tests => 15;
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
-require 'ljlang.pl';
 BEGIN { $LJ::HOME = $ENV{LJHOME}; }
+use LJ::Lang;
 use LJ::Console;
 use LJ::Test qw (temp_user);
 local $LJ::T_NO_COMMAND_PRINT = 1;
diff -r 0b01e0370322 -r e8d5bfb23489 t/faq.t
--- a/t/faq.t	Sat Sep 12 12:14:17 2009 +0800
+++ b/t/faq.t	Sat Sep 12 05:33:57 2009 +0000
@@ -5,7 +5,7 @@ use lib "$ENV{LJHOME}/cgi-bin";
 use lib "$ENV{LJHOME}/cgi-bin";
 
 require 'ljlib.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 
 use LJ::Faq;
 use LJ::Test qw(memcache_stress);
diff -r 0b01e0370322 -r e8d5bfb23489 t/settings.t
--- a/t/settings.t	Sat Sep 12 12:14:17 2009 +0800
+++ b/t/settings.t	Sat Sep 12 05:33:57 2009 +0000
@@ -4,7 +4,7 @@ use Test::More;
 use Test::More;
 use lib "$ENV{LJHOME}/cgi-bin";
 require 'ljlib.pl';
-require 'ljlang.pl';
+use LJ::Lang;
 
 #plan tests => ;
 plan skip_all => 'Fix this test! LJ/Setting/WebpageURL.pm is missing';
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org