plorcly borglar is a "mod" created by jan Misali for the constructed language toki pona that makes words sound funny.

It involves substituting the 14 toki pona letters for funnier versions plus 2 extra rules. The substitutions are:

toki pona letter plorcly borglar letter
m bl
n gl
p b
t pl
k cl
s scr
w spw
l scl
j shl
i ee
u oo
e o
o or
a ar

The two extra rules are:

  • When an "n" in a toki pona is followed by a consonant or ends a word it is replaced with "ng" instead of "gl", so nasin becomes glarscreeng not *glarscreegl
  • When an i is the final letter of a word containing another vowel it becomes "y" instead of "ee", so poki becomes borcly not *borclee, but ni becomes glee not gly.

(The vowels here are "a", "e", "i", "o" and "u", "y" does not exist in toki pona at all)

And that's it.

The challenge here is to take a sequence of space separated words that are valid in toki pona's phonotactics and convert it to plorcly borglar. If you know what that means you can get going, this is so the goal is to minimize the size of your source code as measured in bytes. Otherwise I will explain it.

Toki pona has 14 letters / phonemes, 9 consonants "m", "n", "p", "t", "k", "s", "w", "l", and "j" and 5 vowels "a", "e", "i", "o", "u". All words are built out of these characters (No upper case), but not every sequence of these characters is a valid word.

That's probably all you need to complete this challenge. But there are more rules that might be helpful for optimizing.

Words in toki pona consist of syllables. A syllable in toki pona is consists of a consonant, then a vowel, then an optional "n". The first syllable in a word can skip the initial consonant. For example "olin" is a valid syllable consisting of "o" + "lin". Additionally "nn" and "nm" are not legal sequences so you can't end a syllable in "n" if the next one starts with "n" or "m".

Finally the following syllables are illegal:

  • ti(n)
  • ji(n)
  • wu(n)
  • wo(n)

So they will not appear in input words.

Test cases

toki pona -> plorcly borglar
nasin -> glarscreeng
poki -> borcly
ni -> glee
mi olin e sina -> blee orscleeng o screeglar
soweli lawa li ike tawa mi -> scrorspwoscly sclarspwar sclee eeclo plarspwar blee
lon sike lon ma la jan lili li tomo -> sclorng screeclo sclorng blar sclar shlarng scleescly sclee plorblor
sinpin -> screengbeeng
tenpo -> plongbor
linja -> scleengshlar
  • 5
    \$\begingroup\$ I've tagged this with natural-language, so I will just say before any pedant points it out for me, I know that toki pona is not a natural language, but a constructed language. I still think the tag is appropriate. \$\endgroup\$
    – Wheat Wizard
    Feb 11 at 18:39
  • \$\begingroup\$ This somehow reminds me of that one episode from Friends. You know, the one where Joey speaks French. \$\endgroup\$
    – QBrute
    Feb 13 at 14:33
  • \$\begingroup\$ @WheatWizard I don't think natural-language fits here, but for a different reason. If someone's searching for natural-language specifically, they don't want to see a typical string substitution challenge. Same with parsing, really. This should just be string IMO. \$\endgroup\$ Feb 13 at 18:40

9 Answers 9


Vyxal, 73 68 66 63 62 60 bytes


Try it Online! or verify all test cases.

-3 bytes by emanresu A

-3 bytes by slightly modifying emanresu A's 64 byte solution

-1 byte by adapting mod 17 indexing from @Arnauld's JavaScript answer

-2 bytes by emanresu A

Start by handling the n special case by replacing every occurrence of n where special rules don't apply with g.

‛ng       # push the string "ng"
   kv     # push all vowels "aeiou"
     vẊ   # vectorize cartesian product [["na", "ne", ... ], [ga, ge, ...]]
       ÷  # push each to stack
        V # replace

Process each word individually, optionally replacing a i at the end with y.

⌈              # split on spaces
 ƛ             # map:
  Ṫ            #   remove last item
   A           #   is vowel?
    a[         #   if any is true:
      ṫ        #     tail extract
       ‛iy     #     push "ib"
          *    #     ring translate
           J   #     concatenate
            ;  # end map

Now comes the main part where most of the replacements happen.

C             # convert to a list of char codes
 «...«        # push the string "spw gl y ee shl cl scl bl ng or b  ar scr pl oo o"
      ⌈       # split on spaces
       $      # swap top two items on the stack
        İ     # index into (Vyxal indexing wraps around)
         Ṡ    # vectorizing sum
          Ṅ   # join by spaces
  • \$\begingroup\$ 70 \$\endgroup\$
    – emanresu A
    Feb 11 at 23:55
  • \$\begingroup\$ 64 \$\endgroup\$
    – emanresu A
    Feb 13 at 9:55
  • \$\begingroup\$ 60 \$\endgroup\$
    – emanresu A
    Feb 14 at 4:12

Lexurgy, 134 bytes

i=>y/{a,e,i,o,u} []* _ $
n=>ng/_ {m,n,p,t,k,s,w,l,j,$}

Retina 0.8.2, 103 bytes


Try it online! Link includes test cases. Explanation: Mainly just a series of replacements. The primary ordering is that no replacement string contains letters yet to be translated. Additionally the rules that depend on vowels come after e and u are translated but before i is translated to reduce the number of relevant vowels to check.


05AB1E, 86 bytes


Try it online or verify all test cases.


General explanation:

  1. Split on spaces, and map over the toki pona words.
  2. Do the following replacements (in order): a→ar; o→or; e→o; u→oo; s→scr; l→scl; j→shl; p→b; w→spw; k→cl; t→pl; m→bl; na→gla; no→glo; ni→gli; i→ee.
  3. Replace a trailing 'ee' with 'y' if there are three or more vowels.
  4. Join the plorcly borglar words back with space delimiter, and output the result.

Code explanation:

ð¡                    # Split the (implicit) input-list on spaces
                      # (note: cannot be builtin `#`, since it fails for inputs without spaces)
  ε                   # Map over each toki pona word:
   .•3₁‹uŒœÈĆ•        #  Push compressed string "aoeusljpwktmi"
     S                #  Convert it to a list of characters
      .•1и<Uº•        #  Push compressed string "nanonin"
        2ô            #  Split it into parts of size 2: ["na","no","ni","n"]
          «           #  Merge the two lists together
                     "#  Push compressed string "ar or o oo scr scl shl b spw cl pl bl ee gla gli glo ng"
     #                #  Split it on spaces
   .:                 #  Do all replacement one by one in the current toki pona word
   Âć'eQs¦žMÃĀ*i¨¨'y« #  Handle special case trailing 'i':
   Â                  #   Bifurcate the current word; short for Duplicate & Reverse copy
    ć                 #   Extract head; pop and push remainder-string and first char
     'eQ             '#   Check if this character is an "e"
    s                 #   Swap so the remainder-string is at the top
     ¦                #   Remove the first character
      žMÃ             #   Only keep all vowels
         Ā            #   Check whether what remains is NOT an empty string
          *i          #   If both are truthy:
            ¨¨        #    Remove the trailing "ee"
              'y«    '#    And append an "y" instead
  ]                   # Close both the if-statement and map
   ðý                 # Join the plorcly borglar words back together with space delimiter
                      # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to compress strings not part of the dictionary?) to understand why .•3₁‹uŒœÈĆ• is "aoeusljpwktmi"; .•1и<Uº• is "nanonin"; and .•3r"Δü¾n₂¸}#rö₆!õé¯à1#ry‚H¦±x¦7ke• is "ar or o oo scr scl shl b spw cl pl bl ee gla gli glo ng".


JavaScript, 260 bytes

s=>s.replace(/[^ ]*/g,w=>[...w].map((l,i,a)=>a.some((l,i)=>i!=(v={i:'ee',u:'oo',e:'o',o:'or',a:'ar'},b=a.length-1)&&v[l])&&l=='i'&&i==b?'y':((c={m:'bl',n:'gl',p:'b',t:'pl',k:'cl',s:'scr',w:'spw',l:'scl',j:'shl'})[a[i+1]]||i==b)&&l=='n'?'ng':c[l]||v[l]).join``) // 260

// Without map and join
s=>s.replace(/[^ ]*/g,w=>w.replace(/./g,(l,i)=>[...w].some((l,i)=>i!=(v={i:'ee',u:'oo',e:'o',o:'or',a:'ar'},b=w.length-1)&&v[l])&&l=='i'&&i==b?'y':((c={m:'bl',n:'gl',p:'b',t:'pl',k:'cl',s:'scr',w:'spw',l:'scl',j:'shl'})[w[i+1]]||i==b)&&l=='n'?'ng':c[l]||v[l])) // 260

Try it:

f=s=>s.replace(/[^ ]*/g,w=>[...w].map((l,i,a)=>a.some((l,i)=>i!=(v={i:'ee',u:'oo',e:'o',o:'or',a:'ar'},b=a.length-1)&&v[l])&&l=='i'&&i==b?'y':((c={m:'bl',n:'gl',p:'b',t:'pl',k:'cl',s:'scr',w:'spw',l:'scl',j:'shl'})[a[i+1]]||i==b)&&l=='n'?'ng':c[l]||v[l]).join``)

  ['o', 'or'],
  ['toki pona', 'plorcly borglar'],
  ['nasin', 'glarscreeng'],
  ['poki', 'borcly'],
  ['ni', 'glee'],
  ['tonki', 'plorngcly'],
  ['mi olin e sina', 'blee orscleeng o screeglar'],
  ['soweli lawa li ike tawa mi', 'scrorspwoscly sclarspwar sclee eeclo plarspwar blee'],
  ['lon sike lon ma la jan lili li tomo', 'sclorng screeclo sclorng blar sclar shlarng scleescly sclee plorblor'],
  ['sinpin', 'screengbeeng'],
  ['linja', 'scleengshlar'],
].map(s=>console.log(f(s[0]), f(s[0])===s[1]));


JavaScript (Node.js), 155 bytes


Try it online!


Step 1: main character substitution

Because the input string is guaranteed to be valid toki pona, we only need to support the 14 toki pona letters. Taking their ASCII code modulo 17 is slightly more efficient than just subtracting 97.

character ASCII code mod 17 replaced with
'a' 97 12 'ar'
'e' 101 16 'o'
'i' 105 3 'ee'
'j' 106 4 'shl'
'k' 107 5 'cl'
'l' 108 6 'scl'
'm' 109 7 'bl'
'n' 110 8 'gl'
'o' 111 9 'or'
'p' 112 10 'b'
's' 115 13 'scr'
't' 116 14 'pl'
'u' 117 15 'oo'
'w' 119 0 'spw'

It is worth noting that translated vowels can be detected with just [aeo] because i's and u's are removed entirely. We use this property for the two other steps.

Step 2: applying the n→ng rule

We look for ...

 .--------> the substitution of 'n'
 |     .--> not followed by the substitution of a vowel
 |  ___|___

... and replace each match with ng.

Step 3: applying the ee→y rule

We look for ...

       .-----------> the substitution of a vowel
       |   .-------> optionally followed by non-space characters
       |   |  .----> followed by 'ee'
       |   |  | .--> followed by a space or end-of-line
      _|_ _|_ | |

... and replace each match with y.


Charcoal, 90 bytes

≔aeiouηFη≔⪫⪪θ⁺nι⁺gιθ⪫EE⪪θ ⎇∨⌕⮌ιi›²ΣEη№ιλι⁺…ι⊖Lιy⭆ι§⪪”&⌊↶k⊗3NH₂uXNSuï^Tζ ﹪﹪⮌≔↙Vs⟧;VDw”d⌕βλ 

Try it online! Link is to verbose version of code. Takes input as a newline-terminated string. Explanation:


Get the vowels separately as this saves a byte.


Loop over the vowels and change ns preceding a vowel to gs.

⪫EE⪪θ ...

With the input split into words,...


... if the word ends in an i and contains at least 2 vowels, then replace that i with a y, and...


... replace each letter in the word with a string from a look-up table, wrapping around after p to save space in the table.

The best I could do as an expression was 93 bytes:

⪫EE⪪S ⎇∨⌕⮌ιi›²ΣEaeiou№ιλι⁺…ι⊖Lιy⭆ι∨§⪪”&⌊∨<↨…¶№Pφ↔↖℅³⁰²¹↑|êG~3ψ⌊↓EÞ´”d⌕βλ§⪪nggl²№aeiouy§⁺ι ⊕μ 

Try it online! Link is to verbose version of code. Takes input as a newline-terminated string. Explanation:


With the input split into words...


... if the word ends in an i and contains at least 2 vowels, then replace that i with a y, ...


... replace each letter except n in the word with a string from a look-up table (wrapping around after p to save space in the table), ...

§⪪nggl²№aeiouy§⁺ι ⊕μ

... but replace n with either ng or gl depending on whether it is followed by a vowel.


Haskell + hgl, 152 bytes

k=v"mnptkswlj"$Wr"bl gl b pl cl scr spw scl shl"
f=fo[pY$k,v"iuoae"$Wr"ee oo or ar o",pY'$"ng"<$χn,k<>pM"y"χi++pY f]

Attempt This Online!

This is an approach using hgl's parser library, there may very well be a shorter way to write this using the regular library functions, but I wanted to put this part of the library to the test, since it needs improvement.


This does about how I would expect it to. However there are some holes here to be filled.

  • I don't know why this keeps coming up without me fixing it: I need a function to take a parser and grab the first complete parse.
  • There should be some way to split a string into substrings of a certain size. This would allow me to make Wr"ee oo or ar o" into Wx2"eeoooraro" and Wr"bl gl pl cl scr spw scl shl b" into Fk"l"<"bgpc"++Wx3"scrspwsclshlb".
  • A constant string enumerating the vowels would save bytes here, however with the above optimization, it would no longer be useful.
  • There should be shortcuts for fpM<χ and fpM<ʃ, plus the bajillion variants that I could make of them.
  • There should probably be a shortcut for ʃ" ". p_S works well enough in this case, but it could cost me later down the line.
  • isP should have an infix.
  • A function which combines (<>) an pY could probably be useful, since it's not uncommon to use them in tandem. If done right it could save a few bytes here.
  • There should be a version of isP which runs both the parsers instead of dropping the separators.
  • I have not implemented any sort of string compression algorithm. This would obviously be useful.

I pushed a version with some of these improvements which in total takes of 27 bytes:

128 bytes

f=k?<>v"iuoae"(Wx2"eeoooraro")<>?hhn"ng"<>(k<>hhi"y"++pY f)

Python, 248 bytes

import re
def f(s,S=re.sub,r=''):
 for w in S('gl(?![aeo])','ng',S('\S',lambda c:'spw///ee/shl/cl/scl/bl/gl/or/b//ar/scr/pl/oo/o'.split('/')[ord(c[0])%17],s)).split():r+=('ee'==w[-2:]and sum(map(w.count,'aeiou'))>2)and w[:-2]+'y 'or w+' '
 return r

Attempt This Online!

Based off Arnauld's JS answer.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.