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

Interpolations without provided value should remain in the translation result #1638

Open
3 tasks done
rubenduiveman opened this issue Nov 22, 2023 · 5 comments
Open
3 tasks done
Labels
Status: Proposal Request for comments

Comments

@rubenduiveman
Copy link

Clear and concise description of the problem

We're upgrading to v9.x. When translating a string, we want to perform some extra logic on the result, using special tags {tag} to be replaced in the translated result. In V8, the translated value still contained those values, but in V9 it doesn't anymore.

Input: { val: "I am a special {tag} string" }, calling t("val").
V8 result: I am a special {tag} string
V9 result: I am a special string

Suggested solution

Let's add an option to createI18n to toggle between those behaviors globally if it's not there.

Alternative

No response

Additional context

No response

Validations

@rubenduiveman rubenduiveman added the Status: Proposal Request for comments label Nov 22, 2023
@JvanderHeide
Copy link

JvanderHeide commented Apr 5, 2024

Currently missing exactly this behaviour.

E.g.: with the following translation key:

en:
  form.validations.xxx: '{label} should be at least {min} characters'

In combination with Zod for validation:

message: i18n.t("form.validations.xxx", {
  min: issue.minimum
})

The actual outcome is: should be at least 1 characters,
Whereas I expect to find {label} should be at least 1 characters, for re-use later on like so:

{{ t(errorMessage, {label, name, inputValue}, {resolvedMessage: true}) }

My current work around is to manually map the missing keys back to their respective placeholders:

message: i18n.t("form.validations.xxx", {
  min: issue.minimum,
  label: '{label}',
  name: '{name}',
  inputValue: '{inputValue}'
})

@youthug
Copy link

youthug commented Dec 11, 2024

Currently missing exactly this behaviour.

E.g.: with the following translation key:

en:
  form.validations.xxx: '{label} should be at least {min} characters'

In combination with Zod for validation:

message: i18n.t("form.validations.xxx", {
  min: issue.minimum
})

The actual outcome is: should be at least 1 characters, Whereas I expect to find {label} should be at least 1 characters, for re-use later on like so:

{{ t(errorMessage, {label, name, inputValue}, {resolvedMessage: true}) }

My current work around is to manually map the missing keys back to their respective placeholders:

message: i18n.t("form.validations.xxx", {
  min: issue.minimum,
  label: '{label}',
  name: '{name}',
  inputValue: '{inputValue}'
})

Interesting solution. But how do you perform tricks like this when the value of the message is unknown (dynamic key)?

@JvanderHeide
Copy link

JvanderHeide commented Dec 11, 2024

@youthug based on your question I felt like going for a bit of a puzzle as it sounded like a nice addition to our work around to be able to that. Here's what I came up with:

utils/i18n/translateKeepMissing.ts

import {
  createParser,
  type MessageNode,
  type NamedNode,
  type Node,
  type PluralNode,
  type ResourceNode,
} from "@intlify/message-compiler";

// See NodeTypes in "@intlify/message-compiler"
// Can't be imported due to '--isolatedModules' flag
const NodeTypes = {
  Resource: 0,
  Plural: 1,
  Message: 2,
  Text: 3,
  Named: 4,
  List: 5,
  Linked: 6,
  LinkedKey: 7,
  LinkedModifier: 8,
  Literal: 9,
} as const;

const isMessageNode = (n: Node): n is MessageNode => {
  return n.type === NodeTypes.Message;
};
const isPluralNode = (n: Node): n is PluralNode => {
  return n.type === NodeTypes.Plural;
};
const isNamedNode = (n: Node): n is NamedNode => {
  return n.type === NodeTypes.Named;
};

const getNamed = (node: ResourceNode) => {
  const placeholders: string[] = [];
  const traverse = (n: MessageNode | PluralNode) => {
    if (isMessageNode(n)) {
      for (const item of n.items) {
        if (isNamedNode(item)) {
          placeholders.push(item.key);
        }
      }
    }
    if (isPluralNode(n)) {
      for (const kase of n.cases) {
        traverse(kase);
      }
    }
  };
  traverse(node.body);
  return placeholders;
};

const p = createParser();

export default (key: string, named?: Record<string, unknown>) => {
  const {tm, rt} = useI18n();
  const rawLabel = tm(key) as string;
  const body = p.parse(rawLabel);

  const names = getNamed(body);
  const providedNamedValues = named ? Object.keys(named) : [];
  const unusedNames = names
    .filter((name) => !providedNamedValues.includes(name))
    .reduce(
      (acc, name) => {
        acc[name] = `{${name}}`;
        return acc;
      },
      {} as Record<string, unknown>,
    );

  return rt(rawLabel, {
    ...named,
    ...unusedNames,
  });
};

Then use it where ever you need it

import translateKeepMissing from "~/utils/i18n/translateKeepMissing";

// Translate the message and provided it with a value for min - that's used if it exists in the label.
// Note that we do not provide it information about the missing properties like 'label' 
const initialTranslation =  translateKeepMissing("form.validations.xxx", {
    min: 3,
}); // results in '{label} should be at least 3 characters'

Then later at any other point you can re-interpolate the string like so

const {t} = useI18n();

const finalTranslation = t(initialTranslation, {
 label: "Subject",
}, {
 resolvedMessage: true,
}); // results in 'Subject should be at least 3 characters'

@youthug
Copy link

youthug commented Dec 12, 2024

@JvanderHeide Thanks for the reply and your great work! I haven't tested the code yet, but it seems a bit tedious. I think it would be better if this was provided as a feature by vue-i18n.

@JvanderHeide
Copy link

JvanderHeide commented Dec 12, 2024

Can't fault you on that thought, I agree. Perhaps I have the time to dive in to that sometime in the future.

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

No branches or pull requests

3 participants