opral / inlang-paraglide-js

Tree-shakable i18n library build on the inlang ecosystem.
https://inlang.com/m/gerre34r/library-inlang-paraglideJs
22 stars 0 forks source link

Computing keys from compile-time constants #164

Open nosovk opened 1 week ago

nosovk commented 1 week ago

I want to start using paraglide in sveltekit, but I have a bit strange use case.

My translation file:

{
  "promotion": "super Promotion",
  "title": "super Title",
  "variation": {
    "promotion": "Promotion variation",
    "title": "Title variation",
  },
  "custom_client": {
    "promotion": "client promotion",
    "title": "client Title",
  }
}

Then in my component: component.svelte

<script>
import { getContext } from 'svelte';
import { t} from 'paraglide';
const i18nPrefix = getContext('settings').i18nPrefix ?? "";
</script>
<h1>{t(i18nPrefix+"promotion")}</h1> <!-- it will produce "custom_client.promotion" key -->
<p>{t(i18nPrefix+"title")}</p>

inside router in svelte I create files like: /src/routes/custom_client/+page.svelte

<script>
import Component from "$lib/Component.svelte";
setContext<LandSettings>("settings", {i18nPrefix: "custom_client." })
</script>
<Component />

In case above we use some prefix constant, that modifies scope of translations, inside some route. Actually we have one landing, but we have dozens of variations with different promotions. Currently we use selfmade translation service, but we have to send full {lang}.json to clients, with all variations. Currently its size is pretty big, and we tried to move to paraglide, because treeshakable translations is what we need.

I've found that there are two different approaches in paraglide - t and m plugins, but wasn't able to make composable key in none of them. Also I found mention that you're not going to support nested messages, that we highly dependant from (https://github.com/opral/inlang-paraglide-js/issues/159). But I see that you use some other message formats, that support nested messages. Probably we can achieve the same with custom source plugin?

nosovk commented 1 week ago

It seems that i18next namespaces should cover that case

LorisSigrist commented 1 week ago

Hi đŸ™‹

The usage like you outlined unfortunately can't work. Any form of dynamic key, like m[prefix + ".promotion"], on the client will break treeshaking. Bundlers won't be able to determine the possible values at build-time.

This would cause all messages to be loaded, regardless of if they are used.

If you want to only load the messages for the current i18nContext you need to render the messages on the server & pass them to the client.

// src/routes/+page.server.ts
import * as m from "$lib/paraglide/messages"

export const load({ locals }) {
  return {
    promotion: m[locals.i18nContext + "_promotion"](),
    title: m[locals.i18nContext + "_title"](),
  }
}
<script>
  export let data;
</script>

<h1>{data.title}</h1>
<p>{data.promotion}</p>

As for nesting: Paraglide does not support nesting, even if a plugin that does support it is used to load the messages.

We recommend to do one of the following:

nosovk commented 5 days ago

Hm, but there is no dynamic key. The key is actually static. Like https://webpack.js.org/plugins/define-plugin/ long time before. And it's actually processed by vite as static string. In my example we use context, which is actually rendered on ssr for example.

I mean that there is no need to make dynamic key load, it should be statically parametrized :)

I was thinking about that namespace thing from https://inlang.com/m/3i8bor92/plugin-inlang-i18next Could it be used to add prefix to all language strings in a context scope?

nosovk commented 2 days ago

Ok, after bit more testing I now understand that even if I use i18next source files with nesting, there is no way to use it in paraglide, because its converted to simple kv. And : which is used to create divider does not supported as literal in function names, which causes paraglide to fail.

LorisSigrist commented 7 hours ago

I just tested the keys with static parameterization & vite does not statically parameterize the keys, even if they are computed only from build-time constants.

It seems that any sort of computation for the key used to index into a namespace will break treeshaking in the current generation of bundlers. Only hard-coded constants work.

LorisSigrist commented 7 hours ago

You're not the only one with this requirement. A while back Eric Burel from the State of JS/HTML survey approached us with a similar use-case. They needed to use the year + the topic of the survey as a namespace, similar to what you're doing here.

Clearly this is something people expect to work. We should open a feature request in Rollup