#Astro 2.10, Fuze.js, Map, i18n...

1 messages · Page 1 of 1 (latest)

magic siren
#

Guys I'm so noob and I dunno what to do... pls help.

#

So I have this code here:

function generateSearchList(results) {
        return results
        .map((r) => {
            const { title, description, image, date, slug } = r.item;
            const dateAsDate = new Date(date);

            return `<li class="mb-14 w-10/12">
                        <div>
                        <time datetime="${dateAsDate.toISOString()}">
                            ${dateAsDate.toLocaleDateString("it-IT", {
                                year: "numeric",
                                month: "long",
                                day: "numeric",
                            })}
                        </time>
                        </div>
                        -
                        <div>
                            <a href={localizePath("/blog/${slug}")}>${t(`all-posts:${title}.title`)}</a>
                        </div>
                        <div>
                            <a href={localizePath("/")}>${t(`all-posts:${description}.subtitle`)} </a>
                        </div>
                        <div>
                            <img
                                src=${image}
                                alt=${image.alt}
                                width={600}
                                height={450}
                                format="webp"
                                fit="cover"
                                quality={90}
                                aspectRatio="1:1"
                                class="relative h-full w-full transform rounded-t-xl object-cover object-center shadow-lg backdrop-opacity-100 transition-all duration-1000 rounded-2xl group-hover:scale-125  group-hover:rounded-2xl"
                            />
                        </div>
                    </li>`;
            })
            .join("");
    }
#

Right now the search bar is working BUT I have localizedPath to set and even translations... I really dunno how to fit everthing inside here...

#

I mean, even if it's typed this ${t(`all-posts:${title}.title`)} it's not actually working... I just get "undefined".

#

And this one here <a href={localizePath("/blog/${slug}")}> it's a 404 page

#

anyone who knows how can I do this?

bitter gyro
#

are you able to share your project? have you followed any specific guide to create all this?

magic siren
#

No, it’s actually a private repo. But the problem it’s just over there.

#

I dunno how to let this things work all together

bitter gyro
#

where are these functions from?

magic siren
#

I try to share the whole page but btw it’s this tutorial right here: https://youtu.be/XnV_2MWqAhQ?si=4zHS1GWzP4LhrSVg

Join the early access list for my course! https://learnastro.dev

Learn how to add fuzzy searching to an SSG Astro site using FuseJS and client-size JavaScript.

In this series, we’ll cover:

  • Lesson 1: Intro and Setup
  • Lesson 2: Add a Search Widget
  • Lesson 3: Sanitize and Redirect Input
  • Lesson 4: Create a Search Route
  • Lesson 5: Generate J...
▶ Play video
magic siren
#
---
import { t, setDefaultNamespace, changeLanguage } from "i18next";
import { Trans } from "astro-i18next/components";
import { localizePath } from "astro-i18next";
import Layout from "../layouts/Layout.astro";
import { Image } from "@astrojs/image/components";

changeLanguage("it");

setDefaultNamespace("services");
--- 

<Layout 
  title={t("common:meta-seo.services-title")} 
  description={t("common:meta-seo.services-desc")}
>

        <main class="container-custom-blog-port">

            <div class="text-center mt-10 mb-10">
                <h1 class="md:text-9xl text-7xl uppercase font-bold text-contentTitle">
                    {t("common:nav.search")}
                </h1>
                <h2 class="md:text-3xl text-lg md:px-0 px-2 font-bold text-contentSubtitle tracking-wide">
                    {t("common:search.search-sub")}
                </h2>
            </div>

            <aside class="container mx-auto grid gap-2">
                <div class="flex justify-center items-center">
                    <label for="search">Search the Blog</label>
                    <span>Enter a search term or phrase to search the blog.</span>
                </div>
                <input
                class="p-2 border-2 border-contentTitle rounded-2xl"
                type="search"
                required
                min="2"
                max="24"
                name="search"
                id="search"
                placeholder="&#xF002;"
                style="font-family:Arial, FontAwesome"
            />
            </aside>
            <p class="mt-2 mb-4" id="searchReadout"></p>
            <section aria-label="Search Results">
                    <ul class="mb-10" id="searchResults"></ul>
            </section>
        </main>
    </Layout>
#
<script>
    // imports
    import DOMPurify from "dompurify";
    import Fuse from "fuse.js";
    import { t, setDefaultNamespace, changeLanguage } from "i18next";
    import { localizePath } from "astro-i18next";
    import { interpolate } from "astro-i18next";
    
    

    // changeLanguage("it");

    setDefaultNamespace("all-posts");

    let SEARCH_DATA;
    let FUSE_INSTANCE;
    const FUSE_OPTIONS = {
        includeScore: true,
        shouldSort: true,
        threshold: 0.5,
        keys: [
            {
                name: "title",
                weight: 1,
            },
            {
                name: "description",
                weight: 0.75,
            },
        ],
    };
    

    const SPINNER = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256" id="spinner"><path d="M236,128a108,108,0,0,1-216,0c0-42.52,24.73-81.34,63-98.9A12,12,0,1,1,93,50.91C63.24,64.57,44,94.83,44,128a84,84,0,0,0,168,0c0-33.17-19.24-63.43-49-77.09A12,12,0,1,1,173,29.1C211.27,46.66,236,85.48,236,128Z"></path><style>
        #spinner {
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            100% {
                transform: rotate(360deg);
            }
        }
        </style></svg>`;

    // selectors
    const search = document.querySelector("#search");
    const searchReadout = document.querySelector("#searchReadout");
    const resultsList = document.querySelector("#searchResults");

    ```
#
// functions
    function updateDocumentTitle(search) {
        document.title = search
            ? `Search results for “${search}"`
            : "Search the Blog";
    }

function updateSearchReadout(search) {
        searchReadout.textContent = search
            ? `Search results for “${search}"`
            : "";
    }

function updateSearchPageURL(search) {
        CANT PASTE THIS IT GIVES ME ERROR
    }
#
function generateSearchList(results) {
        return results
        .map((r) => {
            const { title, description, image, date, slug } = r.item;
            const dateAsDate = new Date(date);

            return `<li class="mb-14 w-10/12">
                        <div>
                        <time datetime="${dateAsDate.toISOString()}">
                            ${dateAsDate.toLocaleDateString("it-IT", {
                                year: "numeric",
                                month: "long",
                                day: "numeric",
                            })}
                        </time>
                        </div>
                        -
                        <div>
                            <a href={localizePath("/blog/${slug}")}>${t(`all-posts:${title}.title`)}</a>
                        </div>
                        <div>
                            <a href={localizePath("/")}>${t(`all-posts:${description}.subtitle`)} </a>
                        </div>
                        <div>
                            <img
                                src=${image}
                                alt=${image.alt}
                                width={600}
                                height={450}
                                format="webp"
                                fit="cover"
                                quality={90}
                                aspectRatio="1:1"
                                class="relative h-full w-full transform rounded-t-xl object-cover object-center shadow-lg backdrop-opacity-100 transition-all duration-1000 rounded-2xl group-hover:scale-125  group-hover:rounded-2xl"
                            />
                        </div>
                    </li>`;
            })
            .join("");
    }
#
async function fetchSearchResults(search) {
        if (search.length === 0) return;
        resultsList.innerHTML = SPINNER;
        if (!SEARCH_DATA) {
            try {
                const res = await fetch("/search.json");
                if (!res.ok) {
                    throw new Error("Something went wrong… please try again");
                }
                const data = await res.json();
                SEARCH_DATA = data;
            } catch (e) {
                console.error(e);
            }
        }
        if (SEARCH_DATA && !FUSE_INSTANCE) {
            FUSE_INSTANCE = new Fuse(SEARCH_DATA, FUSE_OPTIONS);
        }
        if (!FUSE_INSTANCE) return;
        const searchResult = FUSE_INSTANCE.search(search);
        resultsList.innerHTML =
            searchResult.length > 0
                ? generateSearchList(searchResult)
                : `No results found…`;
    }

    // event listeners
    window.addEventListener("DOMContentLoaded", () => {
        const urlParams = DOMPurify.sanitize(
            new URLSearchParams(window.location.search).get("q")
        );
        fetchSearchResults(urlParams);
        updateDocumentTitle(urlParams);
        updateSearchReadout(urlParams);
        search.value = urlParams;
        search.focus();
    });

    search.addEventListener("input", () => {
        const searchTerm = DOMPurify.sanitize(search.value);
        updateDocumentTitle(searchTerm);
        updateSearchReadout(searchTerm);
        fetchSearchResults(searchTerm);
        updateSearchPageURL(searchTerm);
    });
</script>

#

Hope this help 🥲

magic siren
#

@bitter gyro did u disappear? 🥲🥲🥲🥲

bitter gyro
#

Sorry, I'm away from my laptop ATM so I'm not able to run the code and recreate anything 😅

magic siren
bitter gyro
bitter gyro
fathom mantle
#

Using astro-i18next, i18next requires custom config to be used on the client

magic siren
magic siren
fathom mantle
#

I'll try have a look once I'm done doing my work in maybe an hour

magic siren
magic siren
fathom mantle
# magic siren ```js function generateSearchList(results) { return results .map...

I think the issue there is that localizePath is part of the string, not interpolated itself.

Now you're doing

return `
some content here
<a href={localizePath("/")}>${t('some-key')}</a>
some content there
`

which renders

some content here
<a href={localizePath("/")}>Some key</a>
some content there

But the localizePath is invalid there, it's like part of the markup. What you want to do is interpolate it like so:

return `
some content here
<a href="${localizePath('/')}">${t('some-key')}</a>
some content there
`

which renders

some content here
<a href="/fr/">Some key</a>
some content there

You can apply this logic to the rest of your code

magic siren
#

As I've a tried to explain to the begin of the post this stuff seems really stupid but I think it's a lot tricky at the end... and the localizePath it's not the only problem i have

#

cause that ${title} is getting the title inside the mdx WHICH IS JUST a getter for the json file with translations

#

so I don't have the translation itself but just the key name of the json file that than get into the real translated words

fathom mantle
#

Honestly if you could put together a reproduction on stackblitz or codesandbox, that would help us debug with you. Quite hard by just looking at some code

#

There's a lot going on

magic siren
#

Yeah I can understand but the repo is private... if you have anything to suggest I'm open to advice

#

I can share my screen on discord? dunno

fathom mantle
#

the project is private but you don't have to share everything! Take the empty template from https://astro.new/latest and recreate the page you just shared by

  • pasting it obviously
  • adding dependencies / integrations required
  • adding a sample search.json

I don't think anything else is required but I'll tell you if that's case, that would really help 🙏

astro.new

Quickly launch example Astro projects in your favorite browser IDE!

magic siren
#

jesus... it's a lot... there's a lot of files... lot of libraries going on...

fathom mantle
#

For instance, you put some static html + head + body instead of having the layout component, you can simplify it to gain time and avoid sharing too much

magic siren
#

Hmmmm i have an idea

fathom mantle
#

From what I see, the really important bricks are astro-i18next i18next dompurify fuse.js

magic siren
#

maybe it can work easy

#

It's a functional website created from Coding-In-Public

#

We can just have to add i18 and the json files for translations

fathom mantle
#

ok so basically you made it i18n-friendly + returned some more complex markup

#

in the search page

magic siren
#

absolutely yes

#

just this

#

but this "friendly" is not that friendly

fathom mantle
#

I can guess 😂

#

So your issues are:

  • interpolation stuff happening with localizePath calls
  • translation returns undefined
magic siren
#

Yes, translation return undefined and into the results.map (that u will find in this repo as well) I dunno how to manage localizePath and translations as we said

fathom mantle
#

ok first localizePath

#

nested interpolation are supported so you can do that

                        <div>
                            <a href="${localizePath(`/blog/${slug}`)}">${t(`all-posts:${title}.title`)}</a>
                        </div>
                        <div>
                            <a href="${localizePath("/")}">${t(`all-posts:${description}.subtitle`)}</a>
                        </div>
magic siren
#

try to copy/paste and tell u back

fathom mantle
#

For the t call, I need to know how your locale file is looking. Does it look like so in public/locales/en/all-posts.json? Guessing the filename/location here

{
  "my-post": {
    "title": "My post"
  }
}
magic siren
magic siren
fathom mantle
#

ooooh wait I know

#

do you get any error? maybe in the browser console?

#

basically, I think the localizePath function is not injected in the script. If you want to use it, you may need to import it in the script as well. Not sure if it will work if it's server-side only

magic siren
#

I was trying and nope. I got nothing from console

#

I think i've tried to inject (somehow) it inside the script but I was wrong for sure...

#

if you have any idea how to do it let me know

fathom mantle
#

maybe you can console.log the result of generateSearchList before inserting it? we need to see what shape it has

#

if you want to jump on a call we can do that

magic siren
#

it just stop working... the console log not even appear...

#

if i remove the interpolation as we declare it and use the "vanilla code" i can see the console log with the results

magic siren
fathom mantle
#

would help a lot I think 😅

#

go in the chat channel?

#
const localizePath = (str: string) => `${language}/${str}`
#
window.__language === "it" ? "" : window.__language
#

Ok @bitter gyro need your help there

#

basically the i18next package is not usable in the client script for some reason, but we need to use the t function. AFAIK it's not serializable so we can't pass it with define-vars (https://florian-lefebvre.dev/blog/hydration-in-astro#usage-in-astro) so I don't really know what to do here

Hydration in Astro | Florian Lefebvre's Portfolio

Hydration is always a tricky part in SSR. Here is a quick guide with Astro 3!

bitter gyro
#

sorry, I'm back now. just catching up 😅

bitter gyro
#

both /[slug].astro and /[...slug].astro will resolve a link at that level (e.g. /test)

fathom mantle
#

so ...slug will catch all routes instead right?

bitter gyro
#

yes, but [slug].astro is always resolved before [...slug].astro

#

so if it matches [slug].astro it will use that, then if it doesn't it falls back to [...slug].astro

fathom mantle
#

/blog => [...page]

bitter gyro
#

what's the exact path that is 404ing?

fathom mantle
#

/blog/index.astro
/blog/[...slug].astro

#

my-slug
m-other-slug
a/zgfes/afgesg/afeesg

bitter gyro
#

lol, that's correct!

fathom mantle
#

[...slug] => index
[slug] => [...slug]

bitter gyro
#

does it not have pagination?

#

so [page].astro should work

#

looks like it can't load the layout file

fathom mantle
#

[...page].astro => undefined + 1 or 2 or 3 => handle pagination
[...slug].astro => all slugs, even nested

#

am i right?

bitter gyro
#

I'm not sure if you can have 2 rest param routes in one route (also not sure if pagination needs that)

#

ahh, is he doing custom pagination?

#

by default it doesn't handle undefined?

#

the pagniation function

#

I remember building custom logic for that for my blog

fathom mantle
bitter gyro
#

it works though right?

#

[...slug].astro is definitely the way to go

#

that is another error

#

so ...slug fixes this issue

#

then we have another to look at

#

it's trying to load a layout

#

thanks florian 👋

fathom mantle
#

I've been thinking to the t issue while eating and I think the easiest way here is to simply get rid of it!

#

Basically, you don't need a place to translate those posts because each post contains this data

#

So in your search.json, provide the real title and display it directly

magic siren
#

hmmm so how do i type it?

#
import { getCollection } from "astro:content";

async function getPosts() {
  const posts = (await getCollection("blog")).sort(
    (a, b) => a.data.date.valueOf() - b.data.date.valueOf()
  );
  // return posts;
  return posts.map((post) => ({
    slug: post.slug,
    title: post.data.title,
    description: post.data.description,
    image: post.data.image.src,
    date: post.data.date,
  }));
}

export async function get({}) {
  return new Response(JSON.stringify(await getPosts()), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
    },
  });
}
#

Im trying to read again your messages but I'm not understanding... "provide the real title"... but the ${title} is the real title... I'm not understanding what u'r trying to say

fathom mantle
#

sorry sorry

#

In your function generateSearchList, just use ${title} without calling t

magic siren
#

but it's not working :/ it was like at beginning

#

I got results like this: comp-arch-1

#

that actually IS the title of the mdx file

#

cause the translation is not just the tile but it's the interpolation of different things. In fact to retrieve the correct data, i call t like this for the Post Cards ${t(`all-posts:${title}.title`)}

magic siren
#

@bitter gyro any advice from you? 🥲

bitter gyro
#

where are you calling this from? client or server?

bitter gyro
magic siren
# bitter gyro ahh. with this, you'd have to type it manually in your client scripts. Astro doe...

It's inside this ```js
function generateSearchList(results) {

    console.log(results);
    return results
    .map((r) => {
        const { description, image, date, slug } = r.item;
        const dateAsDate = new Date(date);
        r.item.title = t(`all-posts:${description}.title`);
        return `<li class="mb-14 w-10/12">
                    <div>
                    <time datetime="${dateAsDate.toISOString()}">
                        ${dateAsDate.toLocaleDateString("it-IT", {
                            year: "numeric",
                            month: "long",
                            day: "numeric",
                        })}
                    </time>
                    </div>
                    -
                    <div>
          <a href="${localizePath(`/blog/${slug}`)}">${r.item.title}
                        </a>
                    </div>
                    <div>
                        <a href="${localizePath(`/blog/${slug}`)}">${t(`all-posts:${description}.subtitle`)} </a>
                    </div>
                    <div>
                        <img
                            src=${image}
                            alt=${image.alt}
                            width={600}
                            height={450}
                            format="webp"
                            fit="cover"
                            quality={90}
                            aspectRatio="1:1"
                            class="relative h-full w-full transform rounded-t-xl object-cover object-center shadow-lg backdrop-opacity-100 transition-all duration-1000 rounded-2xl group-hover:scale-125  group-hover:rounded-2xl"
                        />
                    </div>
                </li>`;
                
        })
        .join("");
    }
#

it always give me undefined... I really dunno what to do...

bitter gyro
#

is this function called on the client/server?

magic siren
#

server into script tag

magic siren
#

@fathom mantle when you can help me back let me know... even at the end of the day... thank you again so much

bitter gyro
# magic siren server into script tag

if it's being loaded into a script tag it runs on the client. I'm not sure that will work. like I said earlier, you'd have to bundle the strings into an object ahead of time, and send them serialised to the client

magic siren
#

Sure otterlord... I got everything right now... So much easier now to do it 🤣

bitter gyro
#

are you referring to a comment? this is for nextjs. I think the best way to do this would be to create a json object before hand, with everything you need, then stringify it and make it a data-* attribute. then you can load the data attribute on the client. e.g.

---
const json = ...
---

<div id="json" data-json={JSON.stringify(json)}>
</div>

<script>
const json = JSON.parse(document.querySelector('#json').dataset.json)
</script>
fathom mantle
#

this looks like a good solution. I think there's a way with i18next to load a whole translation file

fathom mantle
fathom mantle
#

ready! go #1009102461904109688

#

cc @magic siren

magic siren
#

comin

fathom mantle
#

src/content/blog/en/design/legla-informatics.mdx
src/content/blog/it/design/legla-informatics.mdx

fathom mantle
#

florian-lefebvre