Rand Stats

L10N::RU

zef:ash

L10N::RU

Russian localization of Raku — a slang that lets a program be written with Russian keywords — мой for my, если for if, скажи for say — and the deparser that prints such a program back out in Russian again.

Version

0.0.1

Synopsis

use L10N::RU;

мой @список = 1..10;
скажи сумма @список;

для @список -> $н {
    скажи $н если $н мод 3 равно 0
}

Install

Either installer takes it, into the same ~/.raku store:

rakupp install L10N::RU
zef install L10N::RU

Description

The use line is the one line that cannot be Russian: until it has run, the slang is not in the grammar and there is nothing to write Russian with. Everything after it is Russian, including the program's own names.

The distribution is two modules generated from one translation table:

modulewhat it is
L10N::RUthe slang — use it and the rest of the file parses as Russian
RakuAST::Deparse::L10N::RUthe reverse — an AST printed back out in Russian

Because both come from that one table they agree by construction: anything the slang accepts, the deparser can write back. The round trip is the clearest way to see what a localization is, and it needs no use at all, because the localization can be named:

my $ast := Q[мой $х = 1].AST("RU");
say $ast.DEPARSE("RU");    # мой $х = 1
say $ast.DEPARSE;          # my $х = 1

The last line is the point: there is no Russian Raku. .AST("RU") builds the same AST the English would have built, and the English is what runs.

use L10N::RU 'no-slangification' loads the two roles without touching the grammar — what a tool that wants to inspect the localization needs, and what this distribution's own test file uses so that it can stay in English.

Both Raku engines run all of this. The handful of places where they differ are collected under Compatibility, and nothing between here and there mentions them.

Running a localized program

rusku программа.raku

rusku is the executor this distribution installs. It re-runs the interpreter that invoked it with -ML10N::RU, so the slang is in place before the file is parsed and a program needs no use line of its own.

The two ways of running a localized program are exclusive. Under rusku the file is Russian from its first character, use included — it is spelled используй there — so a file carrying the English use L10N::RU; of the synopsis above will not compile. Such a file is run directly instead:

raku программа.raku

At a REPL, rusku with no arguments — -M and a prompt — opens a localized session, which needs no use line for the same reason:

$ rusku
> скажи 5;
5
> мой $х = 41; скажи $х + 1;
42

What is translated

groupwhat it holds
block16 of 16if, for, given, while, repeat, …
modifier9 of 9those same words as statement modifiers
scope9 of 9my, our, state, has, constant, …
package5 of 5class, role, grammar, module, package
routine6 of 6sub, method, submethod, token, rule, regex
trait-is16 of 16is rw, is copy, is pure, …
traitmod7 of 7is, does, handles, of, trusts, …
stmt-prefix13 of 13do, try, gather, lazy, start, …
phaser14 of 18ENTER, LEAVE, CATCH, END, …
infix24 of 41the word operators: and, or, cmp, eq, mod, …
core174 of 234sub and method names: say, map, sort, elems, …
named65 of 135named arguments: :key, :delete, :global, …
everything else47 of 136enums, adverbs, pragmas, terms, quote languages
405 of 645

A name with no translation keeps its English spelling and goes on working, so the table is a floor rather than a boundary: pi, BEGIN, gcd, unicmp and the q/qq/rx quote languages are left alone deliberately, because a Russian word for them would be a translation of nothing.

my cannot agree with everything

A Russian possessive agrees with what it modifies — here, with whatever the variable happens to be called — and no single form is right for all of them: мой счётчик, but моя переменная and мои числа. A keyword is one word, so the table names the citation form and that is what the slang accepts:

scope-my   мой

Every variable in this README and under examples/ is therefore named with a masculine singular noun, so that the sample code reads as Russian. That is a constraint on the examples rather than on the language — a variable can be called anything, and only the declaration in front of it reads oddly.

Translated names are reserved

A word in the table means what the table says it means, everywhere a name of that kind can appear — including in code you write yourself. сдвинь is shift, so a method of your own called сдвинь is called as shift at every call site, and is not found:

класс Точка {
    метод сдвинь($на) {  }   # declares сдвинь
}
$точка.сдвинь(5);             # calls .shift — and dies

The same holds for named arguments: з is named-v, so з => 1 arrives as v => 1. Every module in the L10N family works this way — $objekt.drehe reaches .rotate in German too — and the remedy is the same in all of them: do not name your own things after words in the table. Точка.новый(...) finding .new is the same mechanism seen from the useful side.

Regenerating

RU.l10n is the source and lib/ is output. That file lists all 645 keys, the untranslated ones commented out, and carries the instructions at its top. To change a word, edit it and run, from the distribution root — L10N-RU/ in a checkout of this repository, not the repository root:

update-localization

That is the script the L10N distribution installs. It rewrites both modules from the table and then precompiles the slang to check that what it wrote works. Compatibility says which engine it needs.

Examples

Both carry their own use line, so they are run directly rather than through rusku — see Running a localized program:

raku -I lib examples/fizzbuzz.raku

Scope

The 240 untranslated names are the ones where a Russian word would be worse than the English: the quote languages (q, qq, rx, m, s), the regex adverbs that are single letters (:i, :g, :s), the pragmas that name themselves (nqp, isms, precompilation), the system methods (BUILD, TWEAK, ACCEPTS), the meta-operators, and the mathematical terms pi, tau and nano. They are listed, commented out, in RU.l10n; uncommenting one and regenerating is all it takes to change that judgement.

Not attempted in this version: registration upstream. L10N keeps its own table of which executor names and file extensions belong to which localization, so rusku is unknown to L10N.binaries-for-localization and there is no .rus extension a localization would be picked from. Both are one-line additions to the upstream distribution, not to this one.

Not attempted either: Russian error messages or a Russian .gist. The slang is the input side only. скажи Истина prints True, and a program that dies says so in English. Localizing the output is a different project with a different table.

Parked, and worth doing: the other forms of my. L10N's tables already have the notation for it — a |-separated translation means the slang accepts every spelling and the deparser prints the first, so мой|моя|мои|мое|моё is how it would be written, and a variable of any gender would then read correctly. One engine blocks each half of that. BUILDING such a table is Rakudo's job, since it is the only engine that can run the generator, and L10N dies there on the first | it meets: it hands a Seq to RakuAST::Regex::Alternation.new, which takes slurpy positionals, and generation fails with You cannot deparse a Seq instance. RUNNING what it would have built is both engines' job, and Raku++ matches nothing for a token holding an alternation, not even its first spelling — so a table built despite the first problem would cost that engine my altogether. No localization in the family uses the notation, which is why neither has been run into before.

Compatibility

engineversiont/01-russian.t
Rakudov2026.0816/16
Raku++3.28.014/16, 2 todo

Neither version is an established floor — no older engine has been tried.

Both engines run the slang: Str.AST, the grammar mixin, a plain use L10N::RU; at the top of a file, the executor, and the examples. What follows is every place the two part company, keyed to the section above that runs into it.

Synopsis, Running a localized program, Examples. A Rakudo up to v2026.08 needs RAKUDO_RAKUAST=1 in the environment. Without it the file goes to the legacy grammar, which has no slang to mix into, and the synopsis stops at Variable '@список' is not declared. RakuAST is the default from v2026.09, and Raku++ needs no such switch at any version. rusku sets it either way, which is why the sections above never mention it.

Description — the round trip. DEPARSE($localization) is Rakudo's. Raku++ takes the localization and ignores it, answering my $х = 1 to both lines: its deparsing role loads there and is correct, it is simply never reached.

Running a localized program — the REPL. Raku++ carries a session's language across its lines, so use L10N::RU; typed at a prompt works there and goes on working. Rakudo puts the slang into the compilation unit the use ran in, and every line a prompt reads is its own unit, so there the typed form is forgotten by the next prompt — as it is for every module in the family, and why the section above reaches for the executor instead.

Regenerating. update-localization needs Rakudo. Raku++ runs the modules it generates but cannot load L10N itself, stopping at use L10N with the module registered no slang.

A private attribute named outside the Latin script is unreachable under Raku++. класс Точка { имеет $.х; метод м() { $!х } } fails with Undefined routine 'х', and $!х += 1 with Target is not assignable. The same class written in plain English with the same Cyrillic name fails identically, so it is about script rather than about slangs — a Latin-script language's diacritics are fine, $!vērt works where $!знач does not. It is why examples/wordcount.raku runs only under Rakudo.

Two more that nothing above runs into. Under Raku++, subst checks its adverbs against the engine's own regex-adverb names before any localization is applied, so "a.b.c".замени(".", "-", :глобально) is rejected with Unrecognized regex adverb — an English :global is what it wants, and why examples/wordcount.raku reaches for вычеши (comb) instead. And a method call on a Range inside code compiled through .AST returns a Range instead of dispatching: Q[('a'..'z').elems].AST.EVAL is 0..1 rather than 26.

Author

Andrew Shitov (zef:ash).

Licence

Artistic-2.0.