admin管理员组

文章数量:1310254

I want to create an astro ponent taking into account a variable in localstorage to get each ponent text, but Astro shows null.

let locale = "en"; //default
const userLanguage = localStorage.getItem("language");
if (userLanguage ) {
locale = userLanguage;
} let menu;
import(`../locale/${locale}/menu.json`).then((lang) =\> {
menu  = lang.default;
});

I need to find the way to have language based json or markup files and load each user's language. I was thinking about using svelte/react but I will have to create a lot of calls or maybe is there another way to make it?

I want to create an astro ponent taking into account a variable in localstorage to get each ponent text, but Astro shows null.

let locale = "en"; //default
const userLanguage = localStorage.getItem("language");
if (userLanguage ) {
locale = userLanguage;
} let menu;
import(`../locale/${locale}/menu.json`).then((lang) =\> {
menu  = lang.default;
});

I need to find the way to have language based json or markup files and load each user's language. I was thinking about using svelte/react but I will have to create a lot of calls or maybe is there another way to make it?

Share Improve this question edited Mar 1, 2024 at 16:01 VLAZ 29.1k9 gold badges63 silver badges84 bronze badges asked Feb 21, 2023 at 15:31 Chris_xDChris_xD 311 silver badge3 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 9

Why it happens

As I understood you want to create *.astro ponent and use localStorage API within it. However, browser related API (such as document and window) is not accessible on the server i.e. in Astro and from MDN you can see that localStorage is part of window object.

The localStorage read-only property of the window interface allows you to access a Storage object for the Document's origin; the stored data is saved across browser sessions.

With that in mind the right usage of localStorage will be window.localStorage which will cause the following Astro error:

document (or window) is not defined

From Astro docs you can see what this actually means:

Astro ponents run on the server, so you can’t access these browser-specific objects within the frontmatter.

Potential solutions

So the potential solution will be to use Framework ponents with lifecycle hooks (e.g React's useEffect, Vue's onMounted and so on) or <script> as mentioned in Astro docs as well:

If the code is in an Astro ponent, move it to a <script> tag outside of the frontmatter. This tells Astro to run this code on the client, where document and window are available.

If the code is in a framework ponent, try to access these objects after rendering using lifecycle methods ... Tell the framework ponent to hydrate client-side by using a client: directive, like client:load, to run these lifecycle methods.

How would I solve it

Hovewer, from my experience I would move the async loading of json translation from the client to the server by just loading all the translations, i.e for each language.

Let's say you have the following folder structure for translations:

- locales
--- menu
----- en.json
----- ru.json
----- es.json
--- other_feature
----- en.json
----- ru.json
----- es.json  

Then we can use glob import to import everything at once:

const translations = import.meta.glob('./locales/menu/*.json', { eager: true, import: 'default' })

Then you just pass this translations object (which is object with keys representing path to file and values representing the json string) to your Framework ponent. You can learn more about glob import here.

Framework ponent itself should use lifecycle method to access the localStorage to read user locale and conditionally take the correct translation from the input props. Below the Vue example:

<script setup>
import { onMounted, ref } from 'vue'

const props = defineProps(['translations'])
const translation = ref({})

onMounted(() => {
  const userLocale = window.localeStorage.getItem("language")
  // take the correct translation from all translations
  translation.value = JSON.parse(
    translations[Object.keys(translations).find(key => key.includes(userLocale))]
  )
})
</script>

<template>
  <p>This message displayed in your mother tongue: {{ translation.message }}</p>
</template>

So the final Astro file can look like this:

---
const translations = import.meta.glob('./locales/menu/*.json', { eager: true, import: 'default' })
---

<div>
  <!-- Keep in mind that using `client:load` you might face hydration issues. They can be resolved by explicitly rendering the ponent on the client using `client:only` -->
  <VueMessageComponent translations={ translations } client:load />
</div>

I hope it helps but keep in mind that I wrote that in JavaScript (not in TypeScript) which can cause some issues with null/undefined values. Also, I did not test this code so it might not work just out of the box :)

本文标签: javascriptMultilanguageAstro amp LocalStorageStack Overflow