Release 0.25: Native Deno support, I18N for Svelte/React/Solid

Today we're announcing the availability of the Primate 0.25 preview release. This release introduces native Deno support, meaning Primate now supports all the three significant runtimes in JS space (Node, Deno, Bun).

In addition, this release introduces @primate/i18n, an internationalization module with a unified API for our Svelte, React and Solid frontend modules.

If you're new to Primate, we recommend reading the Getting started page to get an idea of the framework.

Native Deno support

In our last release, we introduced native support for Bun, allowing you to run Primate with bun --bun x primate, benefitting from significant speed gains introduced by Bun.

This release brings the number of runtimes we support up to three, adding native Deno support. Native means here that, like with Bun, Primate will try to use native Deno routines (like Deno.serve) wherever possible and otherwise fall back to NPM.

To run your Primate app with Deno, use deno run --allow-all npm:primate.

I18N module for Svelte, React and Solid

This release introduces a new module, @primate/i18n, for handling internationalization across the different frontend frameworks we support. At this stage, this module supports Svelte, React and Solid using a nearly completely unified API.

To add support for multiple languages in your application, first install this module by issuing npm install @primate/i18n. Then, import and initialize it in your Primate configuration file.

primate.config.js
import i18n from "@primate/i18n";

export default {
  modules: [
    i18n(),
  ],
};

To add languages, create a locales directory locales. In this directory, create a JSON file for every locale you would like to support and add keys and translations.

locales/en-US.js
{
  "welcome": "Hi and welcome, {username}",
  "message": "This is my website, feel at home here.",
  "bye": "Bye",
  "switch-language": "Switch language",
  "english": "English",
  "german": "German"
}

Add another locale.

locales/de-DE.js
{
  "welcome": "Hallo und willkommen, {username}",
  "message": "Das ist meine Website. Mache dich hier gemütlich.",
  "bye": "Tschüss",
  "switch-language": "Sprache wechseln",
  "english": "Englisch",
  "german": "Deutsch"
}

Next, use the default deep export from @primate/i18n for your frontend of choice in your component, for example Svelte.

Svelte

components/Home.svelte
<script>
  import t from "@primate/i18n/svelte";

  export let username;
</script>
<h1>{$t("welcome", { username })}</h1>

<p>{$t("message")}</p>

{$t("bye")}~

In the case of Svelte, since the default export exposes a store, you need to subscribe to it by prefixing it with $ wherever you use it.

To switch between locales, use the locale named export and call locale.set with the new locale.

components/Home.svelte
<script>
  import { default as t, locale } from "@primate/i18n/svelte";

  export let username;
</script>
<h1>{$t("welcome", { username })}</h1>

<p>{$t("message")}</p>

{$t("bye")}~

<h3>{$t("switch-language")}</h3>
<div><a on:click={() => locale.set("en-US")}>{$t("English")}</a></div>
<div><a on:click={() => locale.set("de-DE")}>{$t("German")}</a></div>

React and Solid

You can use an almost identical API for React and Solid to achieve the same.

components/Home.jsx
import t from "@primate/i18n/react";
// import t from "@primate/i18n/solid"; // for solid

export default function ({ username }) {
  return <>
    <h1>{t("welcome", { username })}</h1>

    <p>{t("message")}</p>

    {t("bye")}~
  </>;
}

In this case, since the default export exposes a function that returns a state variable, you just use it as is (without prefixing it with $ as with Svelte).

Again, to switch between locales, use the locale named export and call locale.set with the new locale.

import { default as t, locale } from "@primate/i18n/react";
// import { default as t, locale } from "@primate/i18n/solid"; // for solid

export default function ({ username }) {
  return <>
    <h1>{t("welcome", { username })}</h1>

    <p>{t("message")}</p>

    {t("bye")}~

    <h3>{t("switch-language")}</h3>
    <div><a onClick={() => locale.set("en-US")}>{t("English")}</a></div>
    <div><a onClick={() => locale.set("de-DE")}>{t("German")}</a></div>
  </>;
}

This release marks only the introduction of the I18N module. In the future we plan to extend it with access to different translation backends and other features commonly expected in a modern I18N library.

Executing guards just before the route function

The only breaking feature of this release is a change to the order of execution for guards. Previously guards were executed after a route was matched but before all route hook functions and the route function itself were executed. One of the most important route hook is the one used by @primate/store to start a transaction before the route runs and make all transactionalized data stores available to the route as request.store.

This meant that if you needed to access request.store in a guard (for example, to check if a given API key is matched in the database), it would not be available.

This release changes the order of execution such that guards are executed after all route hooks have finished running and right before the route function itself is executed. Guards thus have access to exactly the same request object that the route function would see, including stores.

Other changes

Consult the full changelog for a list of all relevant changes.

Next on the road

Some of the things we plan to tackle in the upcoming weeks are,

This list isn't exhaustive or binding. None, some or all of these features may be included in 0.26, and other features may be prioritized according to feedback.

Fin

If you like Primate, consider joining our channel #primate on irc.libera.chat.

Otherwise, have a blast with the new version!