DEV Community

Antonio
Antonio

Posted on

BeeEngine 2.9: plugins are here, starting with BeeLocale 🌍

Starting with v2.9.0, BeeEngine supports plugins. The engine stays "blind": it doesn't import or know anything about any plugin. Each plugin hooks itself in with attach(engine) and removes itself with detach().

The first official plugin is BeeLocale: a tiny helper to make your canvas games multilingual.

npm install beeengine@2.9.0
Enter fullscreen mode Exit fullscreen mode

1. The problem: hardcoded text

// ❌ Before: to translate the game you have to hunt every string in your code
ctx.fillText('Score: ' + score, 20, 30);
ctx.fillText('Game Over', 300, 200);
ctx.fillText('Press SPACE to restart', 260, 240);
Enter fullscreen mode Exit fullscreen mode

2. The solution: BeeLocale

import { BeeEngine } from 'beeengine';
import { BeeLocale } from 'beeengine/plugins';

const canvas = document.getElementById('game');
const engine = new BeeEngine(canvas, 800, 600);

// All translations in one place
const locale = new BeeLocale({
    language: 'it',   // active language
    fallback: 'en',   // backup language
    strings: {
        en: {
            score: 'Score',
            gameover: 'Game Over',
            restart: 'Press SPACE to restart'
        },
        it: {
            score: 'Punteggio',
            gameover: 'Hai perso!',
            restart: 'Premi SPAZIO per ricominciare'
        }
    }
});

// The plugin registers itself in the engine as 'locale'
locale.attach(engine);
Enter fullscreen mode Exit fullscreen mode

3. Use it wherever you draw text

// ✅ After: only keys in your code
ctx.fillText(locale.t('score') + ': ' + score, 20, 30);
ctx.fillText(locale.t('gameover'), 300, 200);
ctx.fillText(locale.t('restart'), 260, 240);
Enter fullscreen mode Exit fullscreen mode

get(key) does exactly the same as t(key), if you prefer that name:

locale.get('gameover'); // "Hai perso!"
Enter fullscreen mode Exit fullscreen mode

You can also grab it from the engine, anywhere in your game:

const loc = engine.plugin('locale');
loc.t('score'); // "Punteggio"
Enter fullscreen mode Exit fullscreen mode

4. Switch language on the fly

document.getElementById('btn-en').onclick = () => locale.setLanguage('en');
document.getElementById('btn-it').onclick = () => locale.setLanguage('it');

// Or automatically, from the browser language
locale.setLanguage(navigator.language.slice(0, 2)); // "it", "en", "fr"...
Enter fullscreen mode Exit fullscreen mode

The next frame already draws the text in the new language.

5. Fallback: never an empty string

BeeLocale looks up a key in this order:

  1. active language (it)
  2. fallback language (en)
  3. the key itself, left visible
const locale = new BeeLocale({
    language: 'fr',
    fallback: 'en',
    strings: {
        en: { score: 'Score', lives: 'Lives' },
        fr: { score: 'Score' }          // "lives" is missing in French
    }
});

locale.t('score');   // "Score"  → found in French
locale.t('lives');   // "Lives"  → missing in French, falls back to English
locale.t('bonus');   // "bonus"  → missing everywhere, shows the key
Enter fullscreen mode Exit fullscreen mode

A missing translation shows up on screen right away instead of silently disappearing. Empty strings ('') count as missing too.

6. Detaching the plugin

locale.detach();
engine.plugin('locale'); // null
Enter fullscreen mode Exit fullscreen mode

Under the hood: the plugin contract

Every plugin needs just two methods: attach(engine) and detach().

class MyPlugin {
    attach(engine) {
        this.engine = engine;
        engine.registerPlugin('my-plugin', this);
        return this;
    }

    detach() {
        if (this.engine) this.engine.unregisterPlugin('my-plugin');
        this.engine = null;
        return this;
    }
}
Enter fullscreen mode Exit fullscreen mode

A plugin can't call engine.loop(), override update/render, or patch the main loop.

If it needs to run something every frame, it uses onTick, which returns a function to turn the hook off:

class FrameCounter {
    attach(engine) {
        this.engine = engine;
        this.frames = 0;
        engine.registerPlugin('frames', this);
        this.off = engine.onTick(() => { this.frames++; });
        return this;
    }

    detach() {
        if (this.off) this.off();   // always turn the hook off
        if (this.engine) this.engine.unregisterPlugin('frames');
        this.engine = null;
        return this;
    }
}
Enter fullscreen mode Exit fullscreen mode

BeeLocale doesn't use onTick at all: it only works when you call t(), so it costs nothing per frame.


What's next

More plugins are coming, one at a time. Which one would you like to see next? Let me know in the comments! 🐝

Top comments (0)