Documentation Index Fetch the complete documentation index at: https://mintlify.com/abisai7/diccionario-chapin/llms.txt
Use this file to discover all available pages before exploring further.
The Chapinismos search feature is a client-side implementation that filters through all dictionary words in real-time without requiring a backend API.
Search Page Implementation
The search page is located at src/pages/[lang]/buscar.astro and uses Astro’s static site generation with client-side JavaScript for interactivity.
Data Preparation
All words are loaded at build time and serialized to JSON for client-side filtering:
---
import { getCollection } from "astro:content" ;
const { lang } = Astro . params ;
const collectionName = lang === "es" ? "words-es" : "words-en" ;
const allWords = await getCollection ( collectionName );
const wordData = JSON . stringify (
allWords . map (( w ) => ({
slug: w . slug ,
word: w . data . word ,
meaning: w . data . meaning ,
category: w . data . category ,
}))
);
---
This creates a lightweight array with only the essential fields needed for searching.
Search Algorithm
Text Normalization
The search normalizes text to handle accents and case-insensitivity:
function normalizeText ( text ) {
return text
. toLowerCase ()
. normalize ( "NFD" )
. replace ( / [ \u0300 - \u036f ] / g , "" );
}
How it works:
Convert to lowercase: "Guacamaya" → "guacamaya"
Decompose accented characters: "búho" → "búho" (separates accent marks)
Remove diacritical marks: "búho" → "buho"
Example:
normalizeText ( "Búho" ); // Returns: "buho"
normalizeText ( "guacamaya" ); // Returns: "guacamaya"
This allows users to search for “buho” and find “búho”.
Search Function
The core search logic filters across multiple fields:
function search ( query ) {
if ( ! query . trim ()) {
resultsContainer . innerHTML = "" ;
return ;
}
const normalized = normalizeText ( query );
const results = words . filter (( w ) => {
const word = normalizeText ( w . word );
const meaning = normalizeText ( w . meaning );
const category = normalizeText ( w . category );
return (
word . includes ( normalized ) ||
meaning . includes ( normalized ) ||
category . includes ( normalized )
);
});
renderResults ( results );
}
Searchable fields:
word - The Guatemalan slang term
meaning - The definition/meaning
category - Word type (sustantivo, verbo, etc.)
URL Parameter Support
The search supports pre-filling via URL query parameters:
// Search on page load if there's a query in the URL
document . addEventListener ( "astro:page-load" , () => {
const params = new URLSearchParams ( window . location . search );
const query = params . get ( "q" );
if ( query && input ) {
input . value = query ;
search ( query );
}
});
Usage examples:
/es/buscar/?q=chucho - Searches for “chucho”
/en/buscar/?q=verbo - Searches for “verbo”
Results Rendering
No Results State
When no matches are found:
if ( results . length === 0 ) {
resultsContainer . innerHTML = `
<div class="text-center py-12 px-4 border border-theme rounded-lg bg-card">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="text-muted mx-auto mb-4">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<p class="text-theme text-lg font-semibold mb-2">
${ t . noResults }
</p>
<p class="text-muted">
${ t . tryAnother }
</p>
</div>
` ;
return ;
}
Results Grid
Results are displayed in a responsive grid:
const prefix = lang === "es" ? "/es" : "/en" ;
resultsContainer . innerHTML = `
<p class="text-muted mb-4 text-sm">
${ results . length } ${ t . results }
</p>
<div class="grid grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-3.5">
${ results
. map (
( item ) => `
<a href=" ${ prefix } /palabras/ ${ item . slug } /"
class="block no-underline p-4 border border-theme rounded-lg bg-card shadow-theme-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-theme-md"
data-umami-event="Search Result Click"
data-umami-event-word=" ${ item . word } "
data-umami-event-category=" ${ item . category } ">
<div class="flex items-center justify-between mb-2">
<h3 class="m-0 text-xl font-semibold text-theme">
${ item . word }
</h3>
<span class="inline-block py-0.5 px-2 rounded text-xs font-medium text-white" style="background-color: ${ getCategoryColor ( item . category ) } ;">
${ getCategoryLabel ( item . category ) }
</span>
</div>
<p class="m-0 text-secondary text-sm leading-relaxed">
${ item . meaning }
</p>
</a>
`
)
. join ( "" ) }
</div>
` ;
Category Colors
Categories are color-coded for visual distinction:
---
import { getCategoryColor } from "../../utils/categoryColors" ;
const categoryColorsData = JSON . stringify ({
sustantivo: getCategoryColor ( "sustantivo" ),
noun: getCategoryColor ( "noun" ),
verbo: getCategoryColor ( "verbo" ),
verb: getCategoryColor ( "verb" ),
adjetivo: getCategoryColor ( "adjetivo" ),
adjective: getCategoryColor ( "adjective" ),
adverbio: getCategoryColor ( "adverbio" ),
adverb: getCategoryColor ( "adverb" ),
expresion: getCategoryColor ( "expresion" ),
expression: getCategoryColor ( "expression" ),
interjección: getCategoryColor ( "interjección" ),
interjection: getCategoryColor ( "interjection" ),
});
---
In the client script:
function getCategoryColor ( category ) {
return categoryColors [ category ] || "#1e40af" ;
}
Event Handling
Search triggers on every keystroke:
input ?. addEventListener ( "input" , ( e ) => {
search ( e . target . value );
});
Prevent page reload on form submit:
const form = document . querySelector ( "form" );
form ?. addEventListener ( "submit" , ( e ) => {
e . preventDefault ();
if ( input ) {
search ( input . value );
}
});
Analytics Integration
Search results are tracked with Umami analytics:
< a href = "${prefix}/palabras/${item.slug}/"
data-umami-event = "Search Result Click"
data-umami-event-word = "${item.word}"
data-umami-event-category = "${item.category}" >
This tracks:
Which words users click from search results
Category distribution of clicked words
Search effectiveness
Internationalization
Search translations are passed to the client:
< script
is:inline
define:vars = { {
searchTranslations: JSON . stringify ({
noResults: t ( "search.noResults" ),
tryAnother: t ( "search.tryAnother" ),
results: t ( "search.results" ),
categories: {
sustantivo: t ( "search.category.sustantivo" ),
verbo: t ( "search.category.verbo" ),
adjetivo: t ( "search.category.adjetivo" ),
adverbio: t ( "search.category.adverbio" ),
expresion: t ( "search.category.expresion" ),
interjección: t ( "search.category.interjección" ),
noun: t ( "search.category.noun" ),
verb: t ( "search.category.verb" ),
adjective: t ( "search.category.adjective" ),
adverb: t ( "search.category.adverb" ),
expression: t ( "search.category.expression" ),
interjection: t ( "search.category.interjection" ),
},
}),
lang ,
} }
>
All filtering happens in the browser, which:
Provides instant results with no network latency
Works offline after initial page load
Scales well for dictionaries with hundreds of words
May need optimization for thousands of entries
Only essential fields are serialized: {
slug : w . slug ,
word : w . data . word ,
meaning : w . data . meaning ,
category : w . data . category ,
}
Examples, synonyms, and other heavy data are excluded to reduce bundle size.
Currently, search triggers on every keystroke. For larger datasets, consider debouncing: let debounceTimer ;
input ?. addEventListener ( "input" , ( e ) => {
clearTimeout ( debounceTimer );
debounceTimer = setTimeout (() => {
search ( e . target . value );
}, 300 ); // Wait 300ms after user stops typing
});
SEO Implementation
The search page includes structured data:
< BreadcrumbSchema
slot = "schema"
lang = { lang }
siteUrl = { siteUrl }
items = { [{ name: t ( "nav.search" ), url: `/ ${ lang } /buscar/` }] }
/>
< SearchPageSchema
slot = "schema"
lang = { lang }
siteUrl = { siteUrl }
title = { t ( "search.title" ) }
description = { t ( "search.description" ) }
url = { `/ ${ lang } /buscar/` }
/>
This helps search engines understand the page’s purpose and structure.