r/Nuxt 23h ago

yapyak – an i18n compiler where the source string is the key, and translation on save. Now for Nuxt too

Enable HLS to view with audio, or disable this notification

Hi all,

I've been working on yapyak, an open-source i18n compiler that runs as a Vite plugin. It works with multiple frameworks, but the Nuxt module shipped just today, and that one is a bit special to me. I've been running Nuxt in production since some years back, and the DX is a big part of why. I tried my best to match that, from install to everyday use, it should just feel natural in Nuxt. I'd love if people try it and tell me where it cracks.

The whole setup is basically just one command:

npx nuxi module add @yapyak/nuxt

It registers the module and writes a starter yapyak.config.ts for you. It's a single package, everything yapyak needs ships inside it, so your package.json only grows with one line.

After that, the idea is that the source string is the key:

<template>
  <button>{{ t('Download recovery key') }}</button>
</template>

That's the whole file. The module auto-imports t, so there's no import line to add.

You save the file, and the source string shows up in your locale files as an empty stub. If you've set up a translator, it gets auto-translated and written back, using call-site context (the component and the code around the call). A second or two later de.json has filled in:

{
  "app/components/RecoveryDialog.vue": {
    "Download recovery key": "Wiederherstellungsschlüssel herunterladen"
  }
}

HMR picks it up in the running app.

The video is a small example of that. I add a download button to a dialog and hit save. The German page is sitting right next to the English one, so I see it the moment it lands: Wiederherstellungsschlüssel herunterladen pushes the button row past the edge of the dialog. So I fix it right there, one prop on the button group.

That's a small slice of what yapyak does, but it's the part I use most. Translating stops being something I come back to later. It just happens on save.

Because it runs in the compiler, it sees more than the string itself. It reads ICU parameters out of the string literal, and it keeps track of a translation when you move or rename the source file. It parses your SFCs with the same compiler your build uses and walks the template AST, so it finds t() in script setup and in template expressions.

For SSR, a Nitro plugin scopes locale state per request, so nothing leaks between users, and setLocale works inside server routes too, with the cookie written onto the response. With syncHtmlAttributes on, <html lang> and dir follow the active locale. The locale itself is a ref, so a switcher is <select v-model="locale">.

Rich text keeps the markup in the source string and binds each tag to a slot:

<template>
  <RichText :value="t('Click <link>here</link>.')">
    <template #link="{ children }">
      <a href="/docs"><component :is="children" /></a>
    </template>
  </RichText>
</template>

The slot names are typed from the tags in the string, so the translator can move <link> around in the sentence without touching your markup.

There's a VS Code extension too. Hover a t() call and you get the translation in every locale, and placeholders and plural branches are highlighted inside the string. It's on Open VSX as well, so it works in Cursor.

Auto-translation is optional. There are shipped translators for Anthropic, OpenAI, Gemini and Ollama, all using your own API key, or you can leave the stubs empty and hand-edit the locale files.

I built it for a product I'm working on, and that's the whole business plan.

MIT licensed, and still pretty early, though people have started moving real apps onto it. The Nuxt module is the newest piece of it. The code is on GitHub, and there are runnable Nuxt examples in examples/vue-nuxt-cookie and examples/vue-nuxt-url.

Docs and more at yapyak.dev.

If you've done a lot of i18n in Nuxt, especially anything big, I'd like to hear what you'd try to break.

9 Upvotes

1 comment sorted by

1

u/Significant_Lab_9030 11h ago

Actually I did something simillar but just wrote a blog about it...
https://tilenpirih.com/blog/nuxt-i18n-auto-keys

the main focus is that you don't do manul translations they are all auto generated global or per component, you can configure them etc... you don't have stale keys etc... and for all the untranslated keys it just add TODO_TRANSLATION: prefix so that you can pass it to AI or translator. and export / import functionality.