#Astro 2.10, Fuze.js, Map, i18n...
1 messages · Page 1 of 1 (latest)
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?
are you able to share your project? have you followed any specific guide to create all this?
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
where are these functions from?
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...
Let me paste you the page
---
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=""
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 🥲
@bitter gyro did u disappear? 🥲🥲🥲🥲
Sorry, I'm away from my laptop ATM so I'm not able to run the code and recreate anything 😅
I don’t think you need to recreate anything… I’d just like to know how can I interpolate the script from i18next inside a map that return a listItem. I supposed it’s really easy but I can’t figure out by myself
I don't know a ton about i18next, but what url does this return?
I'm not sure, but does i18next work on the client? iirc it's meant to be used on the backend?
Using astro-i18next, i18next requires custom config to be used on the client
I think this is enough https://github.com/florian-lefebvre/portfolio/blob/v5/astro/astro-i18next.config.ts#L7-L13
Dude did you even read what I was asking? My website is already working with i18next, I just dunno how to let it works with fuze.js (a serch bar) and the interpolation in the code I’ve posted…
404 but it's actually not working... i mean every variable inside is not working cause there's a problem with interpolation I can't handle
Nope I didn't look at the whole conversation, only Otterlord last message. No need to be aggressive
I'll try have a look once I'm done doing my work in maybe an hour
Dunno I was trying things... I usually just declare it on top of my astro page
Thank you appreciate it
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
Already tried it this but it's not working... cause actually inside the path there's another interpolation with another variable. So if I write the code as u did the search bar "explode", If I write the code in this way
<div>
<a href="${localizePath('/blog/'+`${slug}'`)}">${t(`all-posts:${title}.title`)}</a>
</div>
is actually "exploding" the same but at least i have no error into the script...
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
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
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
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 🙏
jesus... it's a lot... there's a lot of files... lot of libraries going on...
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
Hmmmm i have an idea
From what I see, the really important bricks are astro-i18next i18next dompurify fuse.js
maybe it can work easy
So I've actually took the search bar tutorial from this: https://github.com/coding-in-public/astro-search-fusejs/tree/lesson-8
It's a functional website created from Coding-In-Public
We can just have to add i18 and the json files for translations
ok so basically you made it i18n-friendly + returned some more complex markup
in the search page
I can guess 😂
So your issues are:
- interpolation stuff happening with localizePath calls
- translation returns undefined
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
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>
try to copy/paste and tell u back
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"
}
}
this is actually not working. If I paste it all the results disappear... i still have the query word into the url but 0 results shown (search bar exploded)
locales > en > all-posts.json > { "topic" : { "title" : "xxx" } }
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
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
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
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
sure, if u want we can have a sharing screen call
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
sorry, I'm back now. just catching up 😅
yeah, you'd have to bundle any strings you thought necessary at build/ssr time.
both /[slug].astro and /[...slug].astro will resolve a link at that level (e.g. /test)
so ...slug will catch all routes instead right?
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
what's the exact path that is 404ing?
/blog/index.astro
/blog/[...slug].astro
my-slug
m-other-slug
a/zgfes/afgesg/afeesg
lol, that's correct!
[...slug] => index
[slug] => [...slug]
does it not have pagination?
so [page].astro should work
looks like it can't load the layout file
[...page].astro => undefined + 1 or 2 or 3 => handle pagination
[...slug].astro => all slugs, even nested
am i right?
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
a problem I saw was the error specifically. something about not loading a layout? I didn't see the full error though
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 👋
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
here I am
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
sorry sorry
In your function generateSearchList, just use ${title} without calling t
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`)}
@bitter gyro any advice from you? 🥲
where are you calling this from? client or server?
ahh. with this, you'd have to type it manually in your client scripts. Astro doesn't have typed fetch requests like Svelte does
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...
is this function called on the client/server?
server into script tag
@fathom mantle when you can help me back let me know... even at the end of the day... thank you again so much
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
Sure otterlord... I got everything right now... So much easier now to do it 🤣
dunno if this page can be helpfull but I'm understanding 0... I really hope Florian will help https://github.com/i18next/next-i18next/issues/1698
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>
this looks like a good solution. I think there's a way with i18next to load a whole translation file
that's what I wanted to know! Really, I think the best plan here is to avoid translating but we can jump on a call to tackle this part
when u have free time I'm here
comin
src/content/blog/en/design/legla-informatics.mdx
src/content/blog/it/design/legla-informatics.mdx
florian-lefebvre