Conversation
This comment was marked as off-topic.
This comment was marked as off-topic.
|
From kmsomebody #3801 (comment): Purely from a DX standpoint as a React dev, I think option Let's take this message as an example: "balance": "You have {#tooltip}{amount} coins{/tooltip}."This message needs the Options such as per-message components ( SuggestionThis suggestion is framework-specific, so the framework adapters would need to extend the compiler. I can only provide feedback for React. I do not know if it's possible to implement the same way for other frameworks. The idea is to not overload the message function, but to change the type of its inputs and the return type depending on the message. Why
|
|
@kmsomebody thanks for the in-depth feedback. I see the appeal of the API "just have one message function and let the compiler change return types as needed". <p>{m.hello({ amount: 5 })}</p>Two downsides:1. Mixing inputs and markup risks namespace collisions"balance": "You have {#amount}{amount} coins{/amount}."// 💥 amount is not a react node
m.hello({ amount: 5 })Question is: How often will that happen? Do we need to optimize for namespace collisions? After all, it can be linted via opral/lix#239. Adding an overload for markup as 3rd argument kinda seems out of question. The API would get ugly. m.hello({ amount: 5 }, {}, {amount: <Component />}2. A compiler flag would be needed for the compilerSeems OK tbh. Question is just if the message returning something like a Svelte|Solid|... etc. node works in all frameworks options: {
+ framework: "react" | svelte | ...
}
|
Can the compiler detect that and throw? I think this should just be considered invalid. |
Yes, it could throw. |
|
I think this is the main feature that prevents people from shifting from other locales solutions to inlang or praglidejs. Here is a simple example: en.json {
"welcome_user": "Welcome {username}!",
}index.tsx {m.welcome_user({
username: () => <strong>{user.username}</strong>
})} |
| ```svelte | ||
| <Message message={m.contact} inputs={{ email: "info@example.com" }}> | ||
| {#snippet link({ children, inputs })} | ||
| <a href={"mailto:" + inputs.email}>{children}</a> |
There was a problem hiding this comment.
If children is used like this it will just become a string, children should either be a component and rendered using <Children /> or a snippet and rendered using {@render children()} in order to allow nesting, or am I missing something here?
The example Vue code looks fine to me, but I think the React code has the same issue; I haven't used either in quite some time and am not really knowledgeable on them.
There was a problem hiding this comment.
Check out the implementation PR opral/paraglide-js#613
Here are the tests files https://github.com/opral/paraglide-js/pull/613/changes#diff-55d36a2983d72f441e2514c366c2101e35fd52c93b105dfd2554f19adee7fe7f
There was a problem hiding this comment.
Wait so I can't use components?
So it would be used like this?
<Message {...} markup={{
a: ({children, options})=>`<a href="${options.href}">${children}</a>`
}} />I would have wanted and expected something like
<Message {...}>
{#snippet a({children, options})}
<MyLink href={options.href}>{@render children()}</MyLink>
{/snippet}
</Message>Without having looked at the internals I threw together some small example which does what I would expect. It is not properly typed, sometimes I hate TypeScript with a passion. This allows using components, button as example, it keeps its onclick property.
messages.ts
export type Placeholder = {
type: "variable";
name: string;
};
export type Markup = {
type: "markup";
markup: string;
content: Content;
};
export type Content = (string | Placeholder | Markup)[];
export type Variables = Record<string, string|number>;
export const myContent: Content = [
"Hello, ",
{ type: "markup", markup: "strong", content: [{ type: "variable", name: "name" }, "!"] },
{ type: "markup", markup: "p", content: ["Welcome.", { type: "markup", markup: "button", content: ["Click me (", { type: "variable", name: "count" }, ")"] }] }
];
export const myVariables: Variables = {
name: "Alice",
count: 5,
};Message.svelte
<script lang="ts">
import type { Snippet } from "svelte";
import Message from "./Message.svelte";
import type { Content, Variables } from "./messages";
type Props = {
content: Content;
variables: Variables;
} & Record<string, Snippet<[{ variables: Variables; children: () => Snippet }]>>;
let { content, variables, ...snippets }: Props = $props();
</script>
{#each content as part}
{#if typeof part === "string"}
{part}
{:else}
{#if part.type === "variable"}
{variables[part.name]}
{:else if part.type === "markup"}
{#snippet children()}
<Message content={part.content} variables={variables} {...snippets} />
{/snippet}
{@render snippets[part.markup]({ variables, children })}
{/if}
{/if}
{/each}+page.svelte (usage)
<script lang="ts">
import Message from "./Message.svelte";
import { myContent, myVariables } from "./messages";
</script>
<Message content={myContent} variables={myVariables}>
{#snippet strong({children, variables})}
<strong>{@render children()}</strong>
{/snippet}
{#snippet p({children, variables})}
<p>{@render children()}</p>
{/snippet}
{#snippet button({children, variables})}
<button onclick={() => myVariables.count += 1}>
{@render children()}
</button>
{/snippet}
</Message>There was a problem hiding this comment.
I would have wanted and expected something like
It's also supporting Svelte 4, which has no runes support -> no snippet. I don't know how Svelte 4 should be supported without using html either.
The only thing I can think of is slots. But it require static slot names, <slot name={frame.name}></slot> is not supported. (tho it has the potential)
There was a problem hiding this comment.
@aarondoet @YUCLing the api should support components in Svelte. I'll look into it. We can drop support for Svelte 4 if a better API requires it.
@YUCLing thanks for the example!
There was a problem hiding this comment.
@aarondoet @YUCLing i updated the svelte component. it uses snippets now. is the API ok?
There was a problem hiding this comment.
From what I understand this looks good.
Snippets definitely simplify things a lot here because they are way more versatile than slots, so I definitely support dropping Svelte 4. Definitely looking forward to migrating to inlang and finally dropping my own cursed piece of i18n.
|
Hi! I've made an implementation for Svelte Runes. However, I'm struggling with TypeScript's typing. But I'd wish this can help you with building a better adapter for Svelte. |
Note
Adds a new RFC detailing a safe, framework-agnostic approach for markup placeholders in translations.
message.parts()returning structuredMessagePart[]only for messages with markup (text, markupStart/end/standalone); interpolations emitted as text for injection safety<Message>component (react/vue/svelte) that render via amarkupprop keyed by translator-defined tags, supporting nesting viachildrenrich(), overloaded message fn, per-message components, post-parse) and rationale for choosingparts()+<Message>Written by Cursor Bugbot for commit eec13c3. This will update automatically on new commits. Configure here.