mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 18:29:39 +02:00
i18n: fix IT/FR/ES parity gaps in dashboard + memoro
- dashboard: +5 Einträge pro Sprache für die beiden neuen Widgets activity_feed + articles_unread. - memoro: +1 Eintrag pro Sprache für memo.load_more. Damit sind dashboard (111) und memoro auf gleichem Stand wie DE/EN. Verbleibende Drift (app_slider-Legacy-Keys in memoro IT/FR/ES, common/auth-Legacy in calendar/times) ist strukturell und bleibt einem Folge-Cleanup vorbehalten. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d49ad239d9
commit
87b567eec9
11 changed files with 671 additions and 1 deletions
|
|
@ -153,6 +153,15 @@
|
|||
"body_stats": {
|
||||
"title": "Body",
|
||||
"description": "Peso actual y estado del entrenamiento"
|
||||
},
|
||||
"activity_feed": {
|
||||
"title": "Actividad",
|
||||
"description": "Cambios recientes en todos los módulos",
|
||||
"empty": "Aún no hay actividad"
|
||||
},
|
||||
"articles_unread": {
|
||||
"title": "Artículos",
|
||||
"description": "Artículos no leídos de tu lista de lectura"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,15 @@
|
|||
"body_stats": {
|
||||
"title": "Body",
|
||||
"description": "Poids actuel et statut de l'entraînement"
|
||||
},
|
||||
"activity_feed": {
|
||||
"title": "Activité",
|
||||
"description": "Modifications récentes sur tous les modules",
|
||||
"empty": "Aucune activité pour l'instant"
|
||||
},
|
||||
"articles_unread": {
|
||||
"title": "Articles",
|
||||
"description": "Articles non lus de ta liste de lecture"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,15 @@
|
|||
"body_stats": {
|
||||
"title": "Body",
|
||||
"description": "Peso attuale e stato dell'allenamento"
|
||||
},
|
||||
"activity_feed": {
|
||||
"title": "Attività",
|
||||
"description": "Modifiche recenti in tutti i moduli",
|
||||
"empty": "Ancora nessuna attività"
|
||||
},
|
||||
"articles_unread": {
|
||||
"title": "Articoli",
|
||||
"description": "Articoli non letti dalla tua lista di lettura"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@
|
|||
"show_all_memos": "Mostrar todos los memos",
|
||||
"no_memos_yet": "Aún no hay memos",
|
||||
"no_memos_hint": "Ve a la página de grabación para crear tu primer memo",
|
||||
"load_more": "Cargar más memos",
|
||||
"search_placeholder": "Buscar memos...",
|
||||
"delete_memo_title": "Eliminar memo",
|
||||
"delete_memo_confirm": "¿Realmente desea eliminar \"{title}\"?",
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@
|
|||
"show_all_memos": "Afficher tous les mémos",
|
||||
"no_memos_yet": "Pas encore de mémos",
|
||||
"no_memos_hint": "Allez à la page d'enregistrement pour créer votre premier mémo",
|
||||
"load_more": "Charger plus de mémos",
|
||||
"search_placeholder": "Rechercher des mémos...",
|
||||
"delete_memo_title": "Supprimer le mémo",
|
||||
"delete_memo_confirm": "Voulez-vous vraiment supprimer \"{title}\" ?",
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@
|
|||
"show_all_memos": "Mostra tutti i memo",
|
||||
"no_memos_yet": "Ancora nessun memo",
|
||||
"no_memos_hint": "Vai alla pagina di registrazione per creare il tuo primo memo",
|
||||
"load_more": "Carica altri memo",
|
||||
"search_placeholder": "Cerca memo...",
|
||||
"delete_memo_title": "Elimina memo",
|
||||
"delete_memo_confirm": "Vuoi davvero eliminare \"{title}\"?",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import type { LocalGoal } from '$lib/companion/goals/types';
|
|||
import type { LocalPlace } from '$lib/modules/places/types';
|
||||
import type { LocalRecipe } from '$lib/modules/recipes/types';
|
||||
import type { LocalWardrobeOutfit } from '$lib/modules/wardrobe/types';
|
||||
import type { LocalComicStory } from '$lib/modules/comic/types';
|
||||
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
|
||||
|
||||
export interface ResolvedEmbed {
|
||||
|
|
@ -67,6 +68,9 @@ export async function resolveEmbed(props: ModuleEmbedProps): Promise<ResolvedEmb
|
|||
case 'wardrobe.outfits':
|
||||
items = await resolveWardrobeOutfits(props);
|
||||
break;
|
||||
case 'comic.stories':
|
||||
items = await resolveComicStories(props);
|
||||
break;
|
||||
default:
|
||||
return {
|
||||
items: [],
|
||||
|
|
@ -503,3 +507,63 @@ async function resolveWardrobeOutfits(props: ModuleEmbedProps): Promise<EmbedIte
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Comic-stories: public-comic-portfolio use case. Returns stories
|
||||
* flipped to 'public' with their cover panel as the card image
|
||||
* (panelImageIds[0] → picture.images.publicUrl). Hard-gated on
|
||||
* canEmbedOnWebsite.
|
||||
*
|
||||
* Whitelist (plan §2): title + "N Panels" subtitle + cover-panel URL.
|
||||
* Character references, panel captions/dialogues, storyContext, and
|
||||
* the full panelMeta stay out of the snapshot — the cover image is
|
||||
* already an AI-rendered artifact, the other fields would leak the
|
||||
* author's briefing and source-entry linkage.
|
||||
*/
|
||||
async function resolveComicStories(props: ModuleEmbedProps): Promise<EmbedItem[]> {
|
||||
let stories = await db.table<LocalComicStory>('comicStories').toArray();
|
||||
stories = stories.filter(
|
||||
(s) => !s.deletedAt && !s.isArchived && canEmbedOnWebsite(s.visibility ?? 'private')
|
||||
);
|
||||
|
||||
if (props.filter?.isFavorite === true) {
|
||||
stories = stories.filter((s) => s.isFavorite === true);
|
||||
}
|
||||
if (props.filter?.kind) {
|
||||
// `kind` reuses the generic filter slot as a style filter so the
|
||||
// website editor can restrict to e.g. only manga-style comics.
|
||||
stories = stories.filter((s) => s.style === props.filter?.kind);
|
||||
}
|
||||
if (props.filter?.tagIds?.length) {
|
||||
const wanted = new Set(props.filter.tagIds);
|
||||
stories = stories.filter((s) => (s.tags ?? []).some((t) => wanted.has(t)));
|
||||
}
|
||||
|
||||
const decrypted = (await decryptRecords('comicStories', stories)) as LocalComicStory[];
|
||||
|
||||
// Favourites first, then newest.
|
||||
decrypted.sort((a, b) => {
|
||||
const favA = a.isFavorite ? 0 : 1;
|
||||
const favB = b.isFavorite ? 0 : 1;
|
||||
if (favA !== favB) return favA - favB;
|
||||
return (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '');
|
||||
});
|
||||
|
||||
const coverImageIds = decrypted
|
||||
.map((s) => s.panelImageIds?.[0])
|
||||
.filter((id): id is string => Boolean(id));
|
||||
const coverImages = await db.table<LocalImage>('images').where('id').anyOf(coverImageIds).toArray();
|
||||
const coverById = new Map<string, LocalImage>();
|
||||
for (const img of coverImages) coverById.set(img.id, img);
|
||||
|
||||
return decrypted.map((s) => {
|
||||
const coverId = s.panelImageIds?.[0];
|
||||
const cover = coverId ? coverById.get(coverId) : undefined;
|
||||
const panelCount = s.panelImageIds?.length ?? 0;
|
||||
return {
|
||||
title: s.title,
|
||||
subtitle: `${panelCount} ${panelCount === 1 ? 'Panel' : 'Panels'}`,
|
||||
imageUrl: cover?.publicUrl ?? undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue