← All questions

Can I write Raku in my own language?

Yes. Install one of the L10N::* distributions, use it, and the rest of the file is German, Japanese or Afrikaans:

use L10N::DE;
mein @zahlen = 1, 2, 3;
füralle @zahlen -> $z {
    wenn $z %% 2 { sag "$z ist gerade" }
    sonst        { sag "$z ist ungerade" }
}
1 ist ungerade
2 ist gerade
3 ist ungerade

mein is my, füralle is for, wenn/sonst are if/else, sag is say. Your own names — @zahlen, $z — are yours and are never touched.

The modules are not ours; they are Elizabeth Mattijsen's L10N::* family. Install one the usual way:

rakupp install L10N::DE

zef install L10N::DE works too, and installs into the same store both engines read.

Which languages #

Thirteen are published, plus an L10N::Complete bundle. These eleven are verified here, with the spelling each uses for my and for say:

mysay
L10N::AFAfrikaansmy
L10N::CYWelshfydywedyd
L10N::DEGermanmeinsag
L10N::EOEsperantomiadiru
L10N::FRFrenchmadis
L10N::HUHungarianenyémmond
L10N::ITItalianil-miodillo
L10N::JAJapanese私の言う
L10N::NLDutchmijnzeg
L10N::PTPortuguesemeudiga
L10N::ZHChinese局部

L10N::EN exists and is the identity — English is what every other table translates into, so use L10N::EN; changes nothing. Afrikaans is a nice accident: its word for my is my.

On Rakudo you need a flag; here you do not #

The same file above, run by Rakudo 2026.08:

===SORRY!=== Error while compiling prog.raku
Variable '@zahlen' is not declared. Perhaps you forgot a 'sub' if this
was intended to be part of a signature?
------> mein <HERE>@zahlen = 1, 2, 3;

It needs the RakuAST front end, which is not yet the default there:

RAKUDO_RAKUAST=1 raku prog.raku

and then it prints exactly what Raku++ prints. Raku++ needs no flag and has no second front end to switch to — the reason is the whole of the next section.

How it works, and why that matters to you #

Upstream an L10N module is a slang. Its sub EXPORT mixes a role of grammar rules into $*LANG while your file is still being parsed, because Rakudo's compiler front end is itself a Raku grammar. Raku++ has no grammar object to mix into: its front end is a hand-written lexer and parser in C++.

That would be the end of it, except that an L10N slang is not really a grammar change. It is a table of keyword spellings — and Raku++'s lexer hands every keyword to the parser as a plain identifier, deciding nothing about it. So the whole slang is a rewrite of some words in the token stream, between the lexer and the parser, which is exactly where a slang would have acted. The table is read out of the module the same way .AST($lang) reads it.

Three consequences you can see:

It is the whole file from the use onwards, not the enclosing block. Upstream the slang is lexically scoped. Here it is not:

{
    use L10N::DE;
    sag "innen";
}
sag "außen";

Raku++ prints both lines. Rakudo refuses the second one:

===SORRY!=== Error while compiling scoped.raku
Undeclared routine:
    sag used at line 5. Did you mean 'bag', 'say'?

If you want the difference to stop mattering, put the use at the top of the file, which is where it belongs anyway.

It starts where the pragma is. A keyword above the use line is not rewritten — both engines agree, for different reasons:

mein $frueh = 1;
use L10N::DE;
sag $frueh;
Variable '$frueh' is not declared
  in block <unit> at early.raku line 1
      1 | mein $frueh = 1;

The module's own EXPORT still runs, and still fails to find $*LANG. That is expected — the thing it wanted to do has already been done — so Raku++ says nothing about it. It is not silence in general: a module that really fails to load still tells you, and a language that is not installed is still an error.

The one thing that does not translate #

A declarator that changes how the lexer scans what follows it cannot be reached by a rewrite of the tokens, because by then the scanning has happened. In practice that is token and rule: the lexer has to know it is looking at a regex body before it can read one.

use L10N::DE;
grammatik G {
    wertmarke TOP { \d+ }
}
sag G.parse('42') ?? 'ja' !! 'nein';

Raku++:

===SORRY!=== Parse error at line 3: Missing required term after infix
      3 |     wertmarke TOP { \d+ }

Rakudo, under RAKUDO_RAKUAST=1, prints ja.

grammatik (grammar) is fine, and so is methode, klasse, hat and every other declarator that does not change the lexer's mode. German is lucky in one respect: its word for regex is regex, so this works here —

use L10N::DE;
grammatik G {
    regex TOP { \d+ }
}
sag G.parse('42') ?? 'ja' !! 'nein';
ja

This is a real boundary of the approach, not a bug with a fix pending. Moving it would mean teaching the lexer the language before it starts, which is a different design.

Translating a localized program to English #

.AST($lang) parses a string of localized Raku and gives you the tree; .DEPARSE renders it back as ordinary Raku. It is a plain method and needs no pragma:

print Q:to/DE/.AST("DE").DEPARSE;
mein $summe = 1 + 2;
sag $summe;
DE
my $summe = 1 + 2;
say $summe

Both engines print that. This is also the form the L10N distributions' own test suites use, so it is the best-covered part of the feature. A language you do not have says so rather than quietly doing nothing:

`.AST("XX")`: L10N::XX is not installed

Deparsing is not byte-for-byte identical between the engines in every case — Raku++ writes "Hallo, {$name}!" where Rakudo writes "Hallo, $name!", and Rakudo marks a required parameter -> $n! where Raku++ writes -> $n. Same program either way; different rendering of it.

Does the rest of the toolchain understand it? #

Yes. -c, --lint, --ast, --fmt and the compile modes all read a localized file as the program it is. For use L10N::DE; mein $x = 1; sag $x;:

rakupp --ast prog.raku
Program
  Use L10N::DE
  Assign =
    VarExpr $x [decl my]
    IntLit 1
  Call say
    VarExpr $x

mein became a declaration and sag became say — which is the point. Without that, every one of those tools would be reasoning about a pile of unknown identifiers and telling you it was fine.

--fmt is whitespace-only, so it indents and spaces a German file exactly as it would an English one and never touches a keyword. One rule does less: the else-onto-its-own-line rule knows the English else / elsif / orwith / without, so a localized sonst is left where you put it.

A module can be written in a localized Raku too — the rewrite is on the module load path as well as the program one, and EVAL gets it as well. What a module exports is ordinary Raku, so the program that uses it does not have to be in the same language.