Localize custom card / card editor

I am trying to add translations to my custom card, especially for the card editor.
In config_flow this is no problem but it looks like that you can not access the language vars from a custom component inside the JavaScript custom card.

I am using the old vanilla JS System, not the TS or LitElements. And if possible, I want to stay with this because I am not familar with this.

my de.json (working in config flow) has this structure

{
    "config": {
        "error": {
            "station_not_found": "Die angegebene Station wurde nicht gefunden. Bitte überprüfen Sie den Namen und versuchen Sie es erneut."
        },
        "step": {
            "user": {
            "sections": {
                    "advanced_options": {
                       "name": "Erweiterte Einstellungen",
                       "data": {
                          "hidename": "Den Namen der Karte (inkl. Uhr) ausblenden",

Now I want to access the hidename string

const hidenameLabel = this.hass.localize("config.step.user.sections.advanced_options.data.hidename");
console.log("AnotherMVG - hidenameLabel:", hidenameLabel);

but I only get:
AnotherMVG - hidenameLabel: <empty string>

Somehow this is clear because there is no reference to the custom component.

I also played around with a reference like

this.hass.localize("custom_component.another_mvg.config.step.user.sections.advanced_options.data.hidename");
this.hass.localize("custom_components.another_mvg.config.step.user.sections.advanced_options.data.hidename");

but also this is not working

If I check

console.log(this.hass.resources);

there are no translations from custom components available

Localize itself is working, if I try:

console.log(this.hass.localize("ui.card.weather.twice_daily"));

I get a valid translation

If I try to load the de.json by my own

  async loadTranslations() {
    const language = this.hass?.language || "en";
    try {
      const response = await fetch(`/local/custom_components/another_mvg/translations/${language}.json`);
      console.warn("Translation file loaded. ", response);
      return response.ok ? await response.json() : {};
    } catch (error) {
      console.warn("Translation not loaded.");
      return {};
    }
  }

of course this will not work because its the server path

If I try it with

const response = await fetch(`/custom_components/another_mvg/translations/${language}.json`);

or

const response = await fetch(`/another_mvg/translations/${language}.json`);

I am also not able to load the file, I get the 404

Any idea how to access the own translation ?

I theory it could be that easy if HA loads all translations from custom components and add a custom_component.name_of_the_addon. in front like custom_component.another_mvg.

See if this does anything:

this.hass.loadBackendTranslation("config", "another_mvg");

Thx for your reply :slight_smile: but its not working

this.hass.loadBackendTranslation("config", "another_mvg");

const hidenameLabel = this.hass.localize("config.step.user.sections.advanced_options.data.hidename");
console.log("AnotherMVG - hidenameLabel:", hidenameLabel);

console.log(this.hass.resources);

Result:

AnotherMVG - hidenameLabel: <empty string>

and its still not in the resources

If I try

let loadTest = await this.hass.loadBackendTranslation("config", "another_mvg");  
console.log("AnotherMVG - loadTest:", loadTest);

its at least loading the function

AnotherMVG - loadTest: function i(a, o)

Or did I miss to add something ?

Once the load is finished, I think the keys are loaded in the following path:

await this.hass.loadBackendTranslation("config", "another_mvg");

const hidenameLabel = this.hass.localize("component.another_mvg.config.step.user.sections.advanced_options.data.hidename");

Sorry had a typo in previous post, it’s localize("component. not “components

Youi are the man, its working :slight_smile:

AnotherMVG - hidenameLabel: Den Namen der Karte (inkl. Uhr) ausblenden

For the search function, here is a code snippet

class ContentAnotherMVGEditor extends HTMLElement {
  constructor() {
    super();
    this.config = {};
  }

  async connectedCallback() {
    await this.loadTranslations();
    this.render();
  }

  async loadTranslations() {
    // replace 'another_mvg' with the name of your custom_component
    // replace 'config' with the 'anchor point' in your translation file (en.json)
    await this.hass.loadBackendTranslation("config", "another_mvg");
  }

  setConfig(config) {
    this.config = { ...config };
    this.render();
  }

  render() {
    /* your code */

    // replace 'another_mvg' with the name of your custom_component
    // replace 'config' with the 'anchor point' in your translation file (en.json)
    // customize the rest
    const hidenameLabel = this.hass.localize("component.another_mvg.config.step.user.sections.advanced_options.data.hidename");
    console.log("AnotherMVG - hidenameLabel:", hidenameLabel);
    console.log("###########");

   /* your code */
  }
}

To get everything together for the search function, this is the relevant code for the “frontend” of the custom card.

class ContentAnotherMVG extends HTMLElement {
  set hass(hass) {
    if (!this.content) this.loadTranslations(hass);
	this.render(hass);
  }

  async loadTranslations(hass) {
	  await hass.loadBackendTranslation("frontend", "another_mvg");
	  console.log("AnotherMVG - custom translations should be loaded");
  }

  render(hass) {
    if (!this.content) {
      const card        = document.createElement('ha-card');
      this.content      = document.createElement('div');
      this.styleElement = document.createElement('style');
      /* other code */
      card.appendChild(this.styleElement);
      card.appendChild(this.content);
      this.appendChild(card);
  }
  /* other code */
}

@karwosts

there is only one small thing left (I regonized later), the translation for the “card picker”.
I am talking about this code:

// add the card to the list of custom cards for the card picker
window.customCards = window.customCards || []; // Create the list if it doesn't exist.
window.customCards.push({
    type: "content-card-another-mvg",
    name: "AnotherMVG Departure Card",
    description: "Mit dieser Karte kann man sich die Abfahrtzeiten einer Station anzeigen lassen.",
    documentationURL: "https://github.com/Nisbo/another_mvg",
});

I tried to access hass outside of a class direct before the code shown above but its not available by default.
To get the hass object I use this function

async function loadTranslations() {
    var hass = document.querySelector("home-assistant")?.hass;
    
    if (!hass) {
        console.error("Home Assistant not found!");
        return;
    }

    await hass.loadBackendTranslation("frontend", "another_mvg");
    await Promise.resolve({});
    console.log("Loaded Resources:", hass.resources);

    // Wait if it last longer 
    const startTime = Date.now();
    while (Date.now() - startTime < 5000) {
        const columnTypeLabel = hass.localize("component.another_mvg.frontend.column_type");
        if (columnTypeLabel && columnTypeLabel !== "") {
            console.log("Translation for column_type:", columnTypeLabel);
            return;
        }
        await new Promise(resolve => setTimeout(resolve, 200)); // Warte 200ms und prüfe erneut
        console.log("2nd Test for Translation for column_type:", columnTypeLabel);

    }

    console.warn("Translation not found after waiting.");
}

loadTranslations();

So far I get the object and can print the resources, but loading the custom translation does not work. Do you have any idea ?

Maybe you can wait until after hass is available, and then modify your existing entry in window.customcards. It won’t be available initially when the source code is first included.