Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Computing keys from compile-time constants #164

Open
nosovk opened this issue Jun 26, 2024 · 18 comments
Open

Computing keys from compile-time constants #164

nosovk opened this issue Jun 26, 2024 · 18 comments

Comments

@nosovk
Copy link

nosovk commented Jun 26, 2024

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 (#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
Copy link
Author

nosovk commented Jun 27, 2024

It seems that i18next namespaces should cover that case

Copy link
Collaborator

LorisSigrist commented Jun 27, 2024

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:

  • Use underscores to separate parts of the messsage. Eg: variation_promotion
  • Using randomly generated IDs for messages & rely on tools like Sherlock to preview the contents of messages inline with your code. (Amazon uses this approach)

@nosovk
Copy link
Author

nosovk commented Jun 28, 2024

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
Copy link
Author

nosovk commented Jul 1, 2024

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.

Copy link
Collaborator

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 LorisSigrist changed the title passing modifier Computing keys from compile-time constants Jul 4, 2024
Copy link
Collaborator

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

@nosovk
Copy link
Author

nosovk commented Jul 5, 2024

https://vitejs.dev/config/build-options#build-minify
vite supports terser, which is does those modifications.

image

it seems that there is a reason to use it for prodcutin builds

@mrIliya
Copy link

mrIliya commented Aug 16, 2024

Yep, we also need that feature, would be nice to have it

@Kowalski0805
Copy link

+1, we are using sveltekit, and we're investigating an opportunity of using paraglide as i18n backbone in our application. Unfortunately, this feature is a dealbreaker in comparison with https://github.com/kaisermann/svelte-i18n

@LorisSigrist
Copy link
Collaborator

LorisSigrist commented Aug 19, 2024

This is absolutely something we want to see, but it's fundamentally a bundler limitation. As bundlers get better this will automatically start to work with no change on paraglide's side.

I did try the following approaches but could not get vite to treeshake reliably:

1. Vite + Terser + constant

Using terser unfortunately doesn't solve this today. The following code doesn't get treeshaken properly.

import * as m from "../paraglide/messages.js"
const year = "2024"
console.log(m[`msg_${year}`])

This is also unlikely to work soon. Consider the situation where the year is imported from another file. In this case you would need to resolve it from a different file before you can optimize this one. You would have to do the bundling step twice. This would require major changes to how our bundlers currently work.

2. Using define to replace values

In vite.config.js you can provide a define option which allows you to replace certain values at build time.

define: {
   __YEAR__: "'2024"
}

Unfortunately this doesn't work either:

import * as m from "../paraglide/messages.js"
console.log(m[`msg_${__YEAR__}`])

However, this approach is the most likely to work soon, since there aren't really any major changes needed to the bundler internals. I'll open some issues.

Update: I've tried the define approach with just EsBuild (instead of vite) & that seems to work. If rollup were to support the same define feature then this would be the way to go

Copy link
Collaborator

I've opened an issue in vite about this: vitejs/vite#17898

@kocetora
Copy link

wow, I've spent 3 hours trying to do that, because its certainly unclear from docs that its not working. There is https://inlang.com/m/3i8bor92/plugin-inlang-i18next in a docs, which clearly supports namespaces, but its actually not working with paraglide :(

@foobar11101011
Copy link

We're using paraglide at the current project, and can't move out. But we also stuck with that problem, when we tried to implement user customizations to bundles. I see some workarounds in Vite, but it does not look like it works with paraglide.

@NataliiaSe
Copy link

Any updates on it? We are now evaluating different options

@samuelstroschein
Copy link
Member

@nosovk @NataliiaSe Do I understand the requirement correctly?

  • you have a whitelabel app/website
  • you want to switch translations based on the customer
  • your current approach involves namespaces which do not work in paraglide js due to complex tree-shaking

@nosovk
Copy link
Author

nosovk commented Jan 4, 2025

Yep, your are correct. More like we have one template, that is duplicated 100-200 times with different translations.

@nosovk
Copy link
Author

nosovk commented Jan 4, 2025

Hope something like that will be in v2 #201

@nosovk
Copy link
Author

nosovk commented Jan 4, 2025

Within #285 I hope it should be possible

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants