ory/elements

Localization of button Text / "already have an account" text

Closed this issue · 6 comments

Preflight checklist

Describe your problem

I need to localize more than the title in <UserAuthCard /> Component (React).
This could also effect other components.

Describe your ideal solution

It would be great if there are some extra props for localizing those strings.

Workarounds or alternatives

.

Version

0.0.1-alpha.28

Additional Context

No response

@Benehiko Is there maybe already a solution for that?
Thanks in advance :-D Torben

Hi @TorbenHammes

Good point! There isn't a good solution to this currently.

Some of the text displayed inside the component comes directly from Kratos, for example form validation errors. In this case the UI could map and handle the data based on the message ID or we could have a proxy rewrite the response and keep such a map of IDs with their respective language inside a separate DB. The latter requires no additional changes to Ory Elements, however, this still then leaves us with components that have only english text inside some of the components, such as a button text.

I think a good solution would be to pass overrides for the sub-components. We can expose the props of the componentes used within UserAuthCard for example, we have something like that here
https://github.com/ory/elements/blob/main/src/react-components/ory/helpers/node.tsx#L29-L37

I would need to check how much work something like that would be, but it would make sense to have. Contributions are also welcome :)

Hey @Benehiko,
thank you for your input. I think I can try to work on this and already got an overview of your code.
Right now it's not possible to define those error messages in Kratos, right? How does such an error message look like? maybe it's possible to create an error depending on the original message from kratos with switch case.

Greetings from Berlin
Torben

Right now it's not possible to define those error messages in Kratos, right?

No, Kratos has them statically mapped.

Here is an example of it for Login messages:
https://github.com/ory/kratos/blob/master/text/message_login.go

These have unique IDs though, so one could override them based on the ID. You can check them out here https://www.ory.sh/docs/kratos/concepts/ui-user-interface#ui-error-codes

Just dropping this here, since it worked for me. But it is not by any means a fix to the problem. I am hardly pro-localization!!
In the selfservice-node example in main.js I added something like this:

/// FORM-TEXT-ALTERING
// GENERAL

// LOGIN
document.getElementsByName("webauthn_login_trigger").forEach((v) => {
  v.addEventListener("click", () => {
    { { { webAuthnHandler } } }
  })
})

document.getElementsByName("method").forEach((v) => { if (v.textContent == "Sign in") { v.innerText = "Anmelden"; } });
Array.from(document.getElementsByTagName("form")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "login-flow") {
    const e = v.nextElementSibling;
    e.childNodes[0].nodeValue = "Du hast noch keinen Account? ";
    e.childNodes[1].childNodes[0].innerText = "Registrieren";
  }
})
Array.from(document.getElementsByTagName("a")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "forgot-password-link") {
    v.innerText = "Passwort vergessen?"
  }
})
Array.from(document.getElementsByTagName("div")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "node/input/identifier") {
    v.childNodes[0].childNodes[0].data = "E-Mail "
  }
})
Array.from(document.getElementsByTagName("div")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "node/input/password") {
    v.childNodes[0].childNodes[0].data = "Passwort "
  }
})

// REGISTRATION
function insertAfter(referenceNode, newNode) {
  referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function insertBefore(referenceNode, newNode) {
  referenceNode.parentNode.insertBefore(newNode, referenceNode);
}

function linkNode(text, link) {
  var aTagParent = document.createElement("div");
  aTagParent.classList = "vpyugz5 vpyugzm vpyugz9 w27ftz1 w27ftz2"
  var aTag = document.createElement("a");
  aTag.href = link;
  aTag.target = "_blank";
  aTag.style = "font-size: 0.85rem; line-height: 1rem; text-decoration: underline;";
  aTag.classList = "w27ftz0"
  var textNode = document.createTextNode(text);
  aTag.appendChild(textNode);
  aTagParent.appendChild(aTag);
  return aTagParent;
}

document.getElementsByName("webauthn_register_trigger").forEach((v) => {
  v.addEventListener("click", () => {
    { { { webAuthnHandler } } }
  })
})

document.getElementsByName("method").forEach((v) => { if (v.textContent == "Sign up") { v.innerText = "Registrieren"; } });

Array.from(document.getElementsByTagName("form")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "registration-flow") {
    const e = v.nextElementSibling;
    e.childNodes[0].nodeValue = "Bereits registriert? ";
    e.childNodes[1].childNodes[0].innerText = "Anmelden";
  }
})
document.getElementsByName("traits.privacy").forEach((v) => {
  const privacyRequiredMessage = 'Bitte lies die Datenschutzbestimmungen!'
  v.setCustomValidity((!v.checked) ? privacyRequiredMessage : '')
  v.setAttribute("onchange", `this.setCustomValidity((!this.checked) ? '${privacyRequiredMessage}' : '')`);
  insertBefore(v.parentElement, linkNode("Datenschutzerklärung", "https://example.com/privacy"));
})
document.getElementsByName("traits.terms").forEach((v) => {
  const termsRequiredMessage = 'Bitte lies und akzeptiere die Nutzungsbedingungen!'
  v.setCustomValidity((!v.checked) ? termsRequiredMessage : '')
  v.setAttribute("onchange", `this.setCustomValidity((!this.checked) ? '${termsRequiredMessage}' : '')`);
  insertBefore(v.parentElement, linkNode("Nutzungsbedingungen", "https://example.com/terms"));
})
Array.from(document.getElementsByTagName("div")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "node/input/password") {
    v.childNodes[0].childNodes[0].data = "Passwort "
  }
})

// RECOVERY
document.getElementsByName("method").forEach((v) => { if (v.textContent == "Submit") { v.innerText = "Absenden"; } });
Array.from(document.getElementsByTagName("div")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "node/input/email") {
    v.childNodes[0].childNodes[0].data = "E-Mail "
  }
})
Array.from(document.getElementsByTagName("form")).forEach((v) => {
  if (v.attributes["data-testid"] && v.attributes["data-testid"].nodeValue == "recovery-flow") {
    const e = v.nextElementSibling;
    e.childNodes[0].nodeValue = "Du weißt dein Passwort doch? ";
    e.childNodes[1].childNodes[0].innerText = "Anmelden";
  }
})

⚠️PLEASE DO NOT START FIXING THIS CODE HERE FOR YOUR USE CASE OR WHATEVER!⚠️
It is a workaround and doesn’t require to be agile or „perfect“!
It’s just in case someone needs this fast and doesn’t want to look into the UI parts this deeply. I also did not cover all views (the account activation etc. are missing), but this could break by updating a version anyway, since I am not sure, if the classes are generated anew on every ory elements release.

Please let’s keep pushing the update, so we don’t need workarounds like this. Localization is a must, in my opinion.

Done with #130