@fifthbell/brokaw 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/carousels.d.ts +1 -0
- package/dist/carousels.js +65 -0
- package/dist/components/live-program/LiveProgram.d.ts +8 -0
- package/dist/components/live-program/LiveProgram.js +526 -0
- package/dist/components/live-program/assets.d.ts +14 -0
- package/dist/components/live-program/assets.js +14 -0
- package/dist/components/live-program/components/Marquee.d.ts +16 -0
- package/dist/components/live-program/components/Marquee.js +88 -0
- package/dist/components/live-program/components/MarqueeCurtain.d.ts +5 -0
- package/dist/components/live-program/components/MarqueeCurtain.js +30 -0
- package/dist/components/live-program/components/WorldClocks.d.ts +19 -0
- package/dist/components/live-program/components/WorldClocks.js +101 -0
- package/dist/components/live-program/components/slides/ArticleSlide.d.ts +14 -0
- package/dist/components/live-program/components/slides/ArticleSlide.js +22 -0
- package/dist/components/live-program/components/slides/CallsignSlide.d.ts +6 -0
- package/dist/components/live-program/components/slides/CallsignSlide.js +49 -0
- package/dist/components/live-program/components/slides/slideStyles.d.ts +1 -0
- package/dist/components/live-program/components/slides/slideStyles.js +64 -0
- package/dist/components/live-program/events.d.ts +34 -0
- package/dist/components/live-program/events.js +167 -0
- package/dist/components/live-program/hooks/useSSE.d.ts +11 -0
- package/dist/components/live-program/hooks/useSSE.js +67 -0
- package/dist/components/live-program/i18n.d.ts +4 -0
- package/dist/components/live-program/i18n.js +290 -0
- package/dist/components/live-program/segments/ArticlesSegment.d.ts +6 -0
- package/dist/components/live-program/segments/ArticlesSegment.js +160 -0
- package/dist/components/live-program/segments/EarthquakeSegment.d.ts +16 -0
- package/dist/components/live-program/segments/EarthquakeSegment.js +130 -0
- package/dist/components/live-program/segments/MarketsSegment.d.ts +12 -0
- package/dist/components/live-program/segments/MarketsSegment.js +87 -0
- package/dist/components/live-program/segments/WeatherSegment.d.ts +15 -0
- package/dist/components/live-program/segments/WeatherSegment.js +184 -0
- package/dist/components/live-program/segments/index.d.ts +6 -0
- package/dist/components/live-program/segments/index.js +6 -0
- package/dist/components/live-program/segments/types.d.ts +23 -0
- package/dist/components/live-program/segments/types.js +1 -0
- package/dist/components/live-program/segments/usePlaylistEngine.d.ts +9 -0
- package/dist/components/live-program/segments/usePlaylistEngine.js +108 -0
- package/dist/components/live-program/utils/broadcastTime.d.ts +12 -0
- package/dist/components/live-program/utils/broadcastTime.js +33 -0
- package/dist/homepage-distributor.d.ts +55 -0
- package/dist/homepage-distributor.js +68 -0
- package/dist/instagram-image-template.d.ts +8 -0
- package/dist/instagram-image-template.js +200 -0
- package/dist/outlet-config.d.ts +23 -0
- package/dist/outlet-config.js +23 -0
- package/dist/renderer.browser.d.ts +2 -0
- package/dist/renderer.browser.js +128 -0
- package/dist/renderer.core.d.ts +9 -0
- package/dist/renderer.core.js +353 -0
- package/dist/renderer.d.ts +3 -0
- package/dist/renderer.js +3 -0
- package/dist/renderer.node.d.ts +2 -0
- package/dist/renderer.node.js +71 -0
- package/dist/types/canonical-article.d.ts +247 -0
- package/dist/types/canonical-article.js +235 -0
- package/dist/utils/sofascore.d.ts +3 -0
- package/dist/utils/sofascore.js +31 -0
- package/package.json +78 -0
- package/src/partial-deps.json +52 -0
- package/src/styles/compiled.css +2 -0
- package/src/templates/layouts/404.hbs +5 -0
- package/src/templates/layouts/article-page.hbs +5 -0
- package/src/templates/layouts/category-page.hbs +5 -0
- package/src/templates/layouts/homepage.hbs +5 -0
- package/src/templates/layouts/link-in-bio.hbs +228 -0
- package/src/templates/layouts/live-story.hbs +5 -0
- package/src/templates/layouts/search-page.hbs +5 -0
- package/src/templates/partials/blocks/audio.hbs +12 -0
- package/src/templates/partials/blocks/data-table.hbs +23 -0
- package/src/templates/partials/blocks/divider.hbs +1 -0
- package/src/templates/partials/blocks/heading.hbs +9 -0
- package/src/templates/partials/blocks/image.hbs +6 -0
- package/src/templates/partials/blocks/info-box.hbs +8 -0
- package/src/templates/partials/blocks/instagram.hbs +28 -0
- package/src/templates/partials/blocks/key-points.hbs +8 -0
- package/src/templates/partials/blocks/list.hbs +13 -0
- package/src/templates/partials/blocks/live-update.hbs +24 -0
- package/src/templates/partials/blocks/pull-quote.hbs +6 -0
- package/src/templates/partials/blocks/rich-text.hbs +1 -0
- package/src/templates/partials/blocks/tiktok.hbs +15 -0
- package/src/templates/partials/blocks/x.hbs +74 -0
- package/src/templates/partials/blocks/youtube.hbs +12 -0
- package/src/templates/partials/components/article-main.hbs +159 -0
- package/src/templates/partials/components/breaking-news/live-updates-column.hbs +29 -0
- package/src/templates/partials/components/breaking-news.hbs +56 -0
- package/src/templates/partials/components/category/header.hbs +5 -0
- package/src/templates/partials/components/category/main-grid.hbs +55 -0
- package/src/templates/partials/components/category/main.hbs +7 -0
- package/src/templates/partials/components/category/more-grid.hbs +26 -0
- package/src/templates/partials/components/editorial-hero.hbs +73 -0
- package/src/templates/partials/components/headline.hbs +15 -0
- package/src/templates/partials/components/hero-editorial.hbs +1 -0
- package/src/templates/partials/components/hero.hbs +1 -0
- package/src/templates/partials/components/home/landing.hbs +111 -0
- package/src/templates/partials/components/home/main.hbs +63 -0
- package/src/templates/partials/components/home/more-stories.hbs +23 -0
- package/src/templates/partials/components/home/must-read.hbs +77 -0
- package/src/templates/partials/components/live-story/main.hbs +229 -0
- package/src/templates/partials/components/not-found/main.hbs +28 -0
- package/src/templates/partials/components/search/main.hbs +420 -0
- package/src/templates/partials/components/snack.hbs +92 -0
- package/src/templates/partials/components/spotlight-hero.hbs +59 -0
- package/src/templates/partials/components/trending.hbs +14 -0
- package/src/templates/partials/components/ui/accordion.hbs +30 -0
- package/src/templates/partials/components/ui/breadcrumb.hbs +16 -0
- package/src/templates/partials/components/ui/icon-button.hbs +19 -0
- package/src/templates/partials/components/ui/loading-spinner.hbs +27 -0
- package/src/templates/partials/components/ui/pagination.hbs +56 -0
- package/src/templates/partials/components/ui/scroll-area.hbs +12 -0
- package/src/templates/partials/components/ui/status-badge.hbs +21 -0
- package/src/templates/partials/footers/footer-full.hbs +79 -0
- package/src/templates/partials/footers/footer-minimal.hbs +5 -0
- package/src/templates/partials/headers/header-main.hbs +397 -0
- package/src/templates/partials/headers/header-minimal.hbs +16 -0
- package/src/templates/partials/nav/nav-categories.hbs +5 -0
- package/src/templates/partials/shell/doc-end.hbs +282 -0
- package/src/templates/partials/shell/doc-start-404.hbs +28 -0
- package/src/templates/partials/shell/doc-start-standard.hbs +68 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const SUPPORTED_LANGUAGE_SET = new Set(['en', 'es', 'it']);
|
|
2
|
+
const eventCacheByKey = new Map();
|
|
3
|
+
let activeCacheKey = '__default__';
|
|
4
|
+
function normalizeLanguageToken(value) {
|
|
5
|
+
if (typeof value !== 'string') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const trimmed = value.trim().toLowerCase();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const normalized = trimmed.split('-')[0];
|
|
13
|
+
if (!SUPPORTED_LANGUAGE_SET.has(normalized)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
function normalizeAllowedLanguages(value) {
|
|
19
|
+
if (!Array.isArray(value)) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const deduped = new Set();
|
|
23
|
+
for (const item of value) {
|
|
24
|
+
const normalized = normalizeLanguageToken(item);
|
|
25
|
+
if (normalized) {
|
|
26
|
+
deduped.add(normalized);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...deduped];
|
|
30
|
+
}
|
|
31
|
+
function getEventLanguage(event) {
|
|
32
|
+
const eventRecord = event;
|
|
33
|
+
const direct = normalizeLanguageToken(eventRecord.language) ?? normalizeLanguageToken(eventRecord.lang) ?? normalizeLanguageToken(eventRecord.locale);
|
|
34
|
+
if (direct) {
|
|
35
|
+
return direct;
|
|
36
|
+
}
|
|
37
|
+
for (const post of event.posts || []) {
|
|
38
|
+
const postRecord = post;
|
|
39
|
+
const postLanguage = normalizeLanguageToken(postRecord.language) ?? normalizeLanguageToken(postRecord.lang) ?? normalizeLanguageToken(postRecord.locale);
|
|
40
|
+
if (postLanguage) {
|
|
41
|
+
return postLanguage;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function filterEventsByLanguage(events, allowedLanguages) {
|
|
47
|
+
if (allowedLanguages.length === 0) {
|
|
48
|
+
return events;
|
|
49
|
+
}
|
|
50
|
+
const allowed = new Set(allowedLanguages);
|
|
51
|
+
return events.filter((event) => {
|
|
52
|
+
const eventLanguage = getEventLanguage(event);
|
|
53
|
+
if (!eventLanguage) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return allowed.has(eventLanguage);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function buildCacheKey(language, allowedLanguages) {
|
|
60
|
+
const languagePart = language ?? 'any';
|
|
61
|
+
const allowedPart = allowedLanguages.length > 0 ? [...allowedLanguages].sort().join(',') : 'all';
|
|
62
|
+
return `${languagePart}__${allowedPart}`;
|
|
63
|
+
}
|
|
64
|
+
export async function fetchEvents(options = {}) {
|
|
65
|
+
const allowedLanguages = normalizeAllowedLanguages(options.allowedLanguages);
|
|
66
|
+
const preferredLanguage = normalizeLanguageToken(options.language);
|
|
67
|
+
const cacheKey = buildCacheKey(preferredLanguage ?? undefined, allowedLanguages);
|
|
68
|
+
const eventsApiUrl = new URL('https://api.monitor.gaulatti.com/events');
|
|
69
|
+
if (preferredLanguage) {
|
|
70
|
+
eventsApiUrl.searchParams.set('language', preferredLanguage);
|
|
71
|
+
}
|
|
72
|
+
const response = await fetch(eventsApiUrl.toString());
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Failed to fetch events: ${response.status}`);
|
|
75
|
+
}
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
const newEvents = filterEventsByLanguage(data.events || [], allowedLanguages);
|
|
78
|
+
const previousCacheEntry = eventCacheByKey.get(cacheKey);
|
|
79
|
+
const previousEvents = previousCacheEntry?.events ?? null;
|
|
80
|
+
let nextEvents;
|
|
81
|
+
if (previousEvents && previousEvents.length > 0) {
|
|
82
|
+
const eventMap = new Map();
|
|
83
|
+
previousEvents.forEach((event) => {
|
|
84
|
+
eventMap.set(event.uuid, event);
|
|
85
|
+
});
|
|
86
|
+
newEvents.forEach((newEvent) => {
|
|
87
|
+
const existingEvent = eventMap.get(newEvent.uuid);
|
|
88
|
+
if (!existingEvent) {
|
|
89
|
+
eventMap.set(newEvent.uuid, newEvent);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const postMap = new Map();
|
|
93
|
+
existingEvent.posts.forEach((post) => {
|
|
94
|
+
postMap.set(post.uuid, post);
|
|
95
|
+
});
|
|
96
|
+
newEvent.posts.forEach((post) => {
|
|
97
|
+
postMap.set(post.uuid, post);
|
|
98
|
+
});
|
|
99
|
+
eventMap.set(newEvent.uuid, {
|
|
100
|
+
...existingEvent,
|
|
101
|
+
title: newEvent.title,
|
|
102
|
+
summary: newEvent.summary,
|
|
103
|
+
status: newEvent.status,
|
|
104
|
+
updated_at: newEvent.updated_at,
|
|
105
|
+
posts_count: newEvent.posts_count,
|
|
106
|
+
posts: Array.from(postMap.values()).sort((a, b) => b.relevance - a.relevance)
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
nextEvents = Array.from(eventMap.values()).sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
nextEvents = newEvents;
|
|
113
|
+
}
|
|
114
|
+
const nextFetchTime = new Date();
|
|
115
|
+
eventCacheByKey.set(cacheKey, {
|
|
116
|
+
events: nextEvents,
|
|
117
|
+
lastFetchTime: nextFetchTime
|
|
118
|
+
});
|
|
119
|
+
activeCacheKey = cacheKey;
|
|
120
|
+
return nextEvents;
|
|
121
|
+
}
|
|
122
|
+
export function getCachedEvents() {
|
|
123
|
+
return eventCacheByKey.get(activeCacheKey)?.events ?? null;
|
|
124
|
+
}
|
|
125
|
+
export function getLastFetchTime() {
|
|
126
|
+
return eventCacheByKey.get(activeCacheKey)?.lastFetchTime ?? null;
|
|
127
|
+
}
|
|
128
|
+
export function clearCachedEvents() {
|
|
129
|
+
eventCacheByKey.clear();
|
|
130
|
+
activeCacheKey = '__default__';
|
|
131
|
+
}
|
|
132
|
+
export function hasEventChanges(oldEvents, newEvents) {
|
|
133
|
+
if (oldEvents.length !== newEvents.length) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
const oldMap = new Map(oldEvents.map((event) => [event.uuid, event]));
|
|
137
|
+
const newMap = new Map(newEvents.map((event) => [event.uuid, event]));
|
|
138
|
+
for (const uuid of newMap.keys()) {
|
|
139
|
+
if (!oldMap.has(uuid)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const uuid of oldMap.keys()) {
|
|
144
|
+
if (!newMap.has(uuid)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const [uuid, newEvent] of newMap.entries()) {
|
|
149
|
+
const oldEvent = oldMap.get(uuid);
|
|
150
|
+
if (!oldEvent) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (oldEvent.title !== newEvent.title ||
|
|
154
|
+
oldEvent.status !== newEvent.status ||
|
|
155
|
+
oldEvent.posts_count !== newEvent.posts_count ||
|
|
156
|
+
oldEvent.posts.length !== newEvent.posts.length) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
const oldPostMap = new Map(oldEvent.posts.map((post) => [post.uuid, post]));
|
|
160
|
+
for (const post of newEvent.posts) {
|
|
161
|
+
if (!oldPostMap.has(post.uuid)) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface UseSSEOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
onMessage?: (data: any) => void;
|
|
4
|
+
reconnectInterval?: number;
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function useSSE({ url, onMessage, reconnectInterval, enabled }: UseSSEOptions): {
|
|
8
|
+
isConnected: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
export function useSSE({ url, onMessage, reconnectInterval = 3000, enabled = true }) {
|
|
3
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
4
|
+
const [error, setError] = useState(null);
|
|
5
|
+
const onMessageRef = useRef(onMessage);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
onMessageRef.current = onMessage;
|
|
8
|
+
}, [onMessage]);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!enabled) {
|
|
11
|
+
setIsConnected(false);
|
|
12
|
+
setError(null);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let disposed = false;
|
|
16
|
+
let eventSource = null;
|
|
17
|
+
let reconnectTimer = null;
|
|
18
|
+
const connect = () => {
|
|
19
|
+
if (disposed) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
eventSource = new EventSource(url);
|
|
24
|
+
eventSource.onopen = () => {
|
|
25
|
+
if (disposed) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
setIsConnected(true);
|
|
29
|
+
setError(null);
|
|
30
|
+
};
|
|
31
|
+
eventSource.onmessage = (event) => {
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(event.data);
|
|
34
|
+
onMessageRef.current?.(data);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error('Failed to parse SSE data:', err);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
eventSource.onerror = () => {
|
|
41
|
+
setIsConnected(false);
|
|
42
|
+
setError(new Error('SSE connection error'));
|
|
43
|
+
eventSource?.close();
|
|
44
|
+
eventSource = null;
|
|
45
|
+
if (!disposed) {
|
|
46
|
+
reconnectTimer = window.setTimeout(connect, reconnectInterval);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
setError(err);
|
|
52
|
+
if (!disposed) {
|
|
53
|
+
reconnectTimer = window.setTimeout(connect, reconnectInterval);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
connect();
|
|
58
|
+
return () => {
|
|
59
|
+
disposed = true;
|
|
60
|
+
if (reconnectTimer !== null) {
|
|
61
|
+
window.clearTimeout(reconnectTimer);
|
|
62
|
+
}
|
|
63
|
+
eventSource?.close();
|
|
64
|
+
};
|
|
65
|
+
}, [enabled, url, reconnectInterval]);
|
|
66
|
+
return { isConnected, error };
|
|
67
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type SupportedLanguage = 'en' | 'es' | 'it';
|
|
2
|
+
export declare function t(key: string, language: SupportedLanguage, replacements?: Record<string, string | number>): string;
|
|
3
|
+
export declare const LANGUAGE_ROTATION: SupportedLanguage[];
|
|
4
|
+
export declare function getNextLanguageIndex(currentIndex: number): number;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
const translations = {
|
|
2
|
+
'segment.articles': {
|
|
3
|
+
en: 'News Articles',
|
|
4
|
+
es: 'Artículos',
|
|
5
|
+
it: 'Articoli'
|
|
6
|
+
},
|
|
7
|
+
'segment.weather': {
|
|
8
|
+
en: 'Weather',
|
|
9
|
+
es: 'Clima',
|
|
10
|
+
it: 'Meteo'
|
|
11
|
+
},
|
|
12
|
+
'segment.earthquakes': {
|
|
13
|
+
en: 'Earthquakes',
|
|
14
|
+
es: 'Terremotos',
|
|
15
|
+
it: 'Terremoti'
|
|
16
|
+
},
|
|
17
|
+
'segment.markets': {
|
|
18
|
+
en: 'Markets',
|
|
19
|
+
es: 'Mercados',
|
|
20
|
+
it: 'Mercati'
|
|
21
|
+
},
|
|
22
|
+
'articles.noArticles': {
|
|
23
|
+
en: 'No articles available',
|
|
24
|
+
es: 'No hay artículos disponibles',
|
|
25
|
+
it: 'Nessun articolo disponibile'
|
|
26
|
+
},
|
|
27
|
+
'weather.header': {
|
|
28
|
+
en: 'WEATHER',
|
|
29
|
+
es: 'CLIMA',
|
|
30
|
+
it: 'METEO'
|
|
31
|
+
},
|
|
32
|
+
'weather.loading': {
|
|
33
|
+
en: 'Loading weather data...',
|
|
34
|
+
es: 'Cargando datos del clima...',
|
|
35
|
+
it: 'Caricamento dati meteo...'
|
|
36
|
+
},
|
|
37
|
+
'weather.high': {
|
|
38
|
+
en: 'High',
|
|
39
|
+
es: 'Máx',
|
|
40
|
+
it: 'Max'
|
|
41
|
+
},
|
|
42
|
+
'weather.low': {
|
|
43
|
+
en: 'Low',
|
|
44
|
+
es: 'Mín',
|
|
45
|
+
it: 'Min'
|
|
46
|
+
},
|
|
47
|
+
'earthquakes.header': {
|
|
48
|
+
en: 'RECENT EARTHQUAKES',
|
|
49
|
+
es: 'TERREMOTOS RECIENTES',
|
|
50
|
+
it: 'TERREMOTI RECENTI'
|
|
51
|
+
},
|
|
52
|
+
'earthquakes.subtitle': {
|
|
53
|
+
en: 'Last 24 Hours • Magnitude 4.5+',
|
|
54
|
+
es: 'Últimas 24 horas • Magnitud 4.5+',
|
|
55
|
+
it: 'Ultime 24 ore • Magnitudo 4.5+'
|
|
56
|
+
},
|
|
57
|
+
'earthquakes.noData': {
|
|
58
|
+
en: 'No significant earthquakes in the last 24 hours',
|
|
59
|
+
es: 'No hay terremotos significativos en las últimas 24 horas',
|
|
60
|
+
it: 'Nessun terremoto significativo nelle ultime 24 ore'
|
|
61
|
+
},
|
|
62
|
+
'earthquakes.loading': {
|
|
63
|
+
en: 'Loading earthquake data...',
|
|
64
|
+
es: 'Cargando datos de terremotos...',
|
|
65
|
+
it: 'Caricamento dati terremoti...'
|
|
66
|
+
},
|
|
67
|
+
'earthquakes.depth': {
|
|
68
|
+
en: 'km deep',
|
|
69
|
+
es: 'km de profundidad',
|
|
70
|
+
it: 'km di profondità'
|
|
71
|
+
},
|
|
72
|
+
'earthquakes.timeAgo.justNow': {
|
|
73
|
+
en: 'Just now',
|
|
74
|
+
es: 'Justo ahora',
|
|
75
|
+
it: 'Proprio ora'
|
|
76
|
+
},
|
|
77
|
+
'earthquakes.timeAgo.minuteAgo': {
|
|
78
|
+
en: '1 minute ago',
|
|
79
|
+
es: 'Hace 1 minuto',
|
|
80
|
+
it: '1 minuto fa'
|
|
81
|
+
},
|
|
82
|
+
'earthquakes.timeAgo.minutesAgo': {
|
|
83
|
+
en: '{count} minutes ago',
|
|
84
|
+
es: 'Hace {count} minutos',
|
|
85
|
+
it: '{count} minuti fa'
|
|
86
|
+
},
|
|
87
|
+
'earthquakes.timeAgo.hourAgo': {
|
|
88
|
+
en: '1 hour ago',
|
|
89
|
+
es: 'Hace 1 hora',
|
|
90
|
+
it: '1 ora fa'
|
|
91
|
+
},
|
|
92
|
+
'earthquakes.timeAgo.hoursAgo': {
|
|
93
|
+
en: '{count} hours ago',
|
|
94
|
+
es: 'Hace {count} horas',
|
|
95
|
+
it: '{count} ore fa'
|
|
96
|
+
},
|
|
97
|
+
'earthquakes.timeAgo.dayAgo': {
|
|
98
|
+
en: '1 day ago',
|
|
99
|
+
es: 'Hace 1 día',
|
|
100
|
+
it: '1 giorno fa'
|
|
101
|
+
},
|
|
102
|
+
'earthquakes.timeAgo.daysAgo': {
|
|
103
|
+
en: '{count} days ago',
|
|
104
|
+
es: 'Hace {count} días',
|
|
105
|
+
it: '{count} giorni fa'
|
|
106
|
+
},
|
|
107
|
+
'markets.header': {
|
|
108
|
+
en: 'MARKETS',
|
|
109
|
+
es: 'MERCADOS',
|
|
110
|
+
it: 'MERCATI'
|
|
111
|
+
},
|
|
112
|
+
'markets.subtitle': {
|
|
113
|
+
en: 'Most Traded',
|
|
114
|
+
es: 'Más Negociados',
|
|
115
|
+
it: 'Più Scambiati'
|
|
116
|
+
},
|
|
117
|
+
'markets.loading': {
|
|
118
|
+
en: 'Loading market data...',
|
|
119
|
+
es: 'Cargando datos del mercado...',
|
|
120
|
+
it: 'Caricamento dati di mercato...'
|
|
121
|
+
},
|
|
122
|
+
'markets.lastUpdate': {
|
|
123
|
+
en: 'Last update: {time}',
|
|
124
|
+
es: 'Última actualización: {time}',
|
|
125
|
+
it: 'Ultimo aggiornamento: {time}'
|
|
126
|
+
},
|
|
127
|
+
'city.NEW YORK': {
|
|
128
|
+
en: 'NEW YORK',
|
|
129
|
+
es: 'NUEVA YORK',
|
|
130
|
+
it: 'NEW YORK'
|
|
131
|
+
},
|
|
132
|
+
'city.LONDON': {
|
|
133
|
+
en: 'LONDON',
|
|
134
|
+
es: 'LONDRES',
|
|
135
|
+
it: 'LONDRA'
|
|
136
|
+
},
|
|
137
|
+
'city.TOKYO': {
|
|
138
|
+
en: 'TOKYO',
|
|
139
|
+
es: 'TOKIO',
|
|
140
|
+
it: 'TOKYO'
|
|
141
|
+
},
|
|
142
|
+
'city.SYDNEY': {
|
|
143
|
+
en: 'SYDNEY',
|
|
144
|
+
es: 'SÍDNEY',
|
|
145
|
+
it: 'SYDNEY'
|
|
146
|
+
},
|
|
147
|
+
'city.ROME': {
|
|
148
|
+
en: 'ROME',
|
|
149
|
+
es: 'ROMA',
|
|
150
|
+
it: 'ROMA'
|
|
151
|
+
},
|
|
152
|
+
'city.MADRID': {
|
|
153
|
+
en: 'MADRID',
|
|
154
|
+
es: 'MADRID',
|
|
155
|
+
it: 'MADRID'
|
|
156
|
+
},
|
|
157
|
+
'city.LIMA': {
|
|
158
|
+
en: 'LIMA',
|
|
159
|
+
es: 'LIMA',
|
|
160
|
+
it: 'LIMA'
|
|
161
|
+
},
|
|
162
|
+
'city.BERLIN': {
|
|
163
|
+
en: 'BERLIN',
|
|
164
|
+
es: 'BERLÍN',
|
|
165
|
+
it: 'BERLINO'
|
|
166
|
+
},
|
|
167
|
+
'city.LOS ANGELES': {
|
|
168
|
+
en: 'LOS ANGELES',
|
|
169
|
+
es: 'LOS ÁNGELES',
|
|
170
|
+
it: 'LOS ANGELES'
|
|
171
|
+
},
|
|
172
|
+
'city.MEXICO CITY': {
|
|
173
|
+
en: 'MEXICO CITY',
|
|
174
|
+
es: 'CIUDAD DE MÉXICO',
|
|
175
|
+
it: 'CITTÀ DEL MESSICO'
|
|
176
|
+
},
|
|
177
|
+
'city.SANTIAGO': {
|
|
178
|
+
en: 'SANTIAGO',
|
|
179
|
+
es: 'SANTIAGO',
|
|
180
|
+
it: 'SANTIAGO'
|
|
181
|
+
},
|
|
182
|
+
'city.BUENOS AIRES': {
|
|
183
|
+
en: 'BUENOS AIRES',
|
|
184
|
+
es: 'BUENOS AIRES',
|
|
185
|
+
it: 'BUENOS AIRES'
|
|
186
|
+
},
|
|
187
|
+
'city.SÃO PAULO': {
|
|
188
|
+
en: 'SÃO PAULO',
|
|
189
|
+
es: 'SÃO PAULO',
|
|
190
|
+
it: 'SAN PAOLO'
|
|
191
|
+
},
|
|
192
|
+
'city.HONOLULU': {
|
|
193
|
+
en: 'HONOLULU',
|
|
194
|
+
es: 'HONOLULU',
|
|
195
|
+
it: 'HONOLULU'
|
|
196
|
+
},
|
|
197
|
+
'city.BEIJING': {
|
|
198
|
+
en: 'BEIJING',
|
|
199
|
+
es: 'PEKÍN',
|
|
200
|
+
it: 'PECHINO'
|
|
201
|
+
},
|
|
202
|
+
'city.SINGAPORE': {
|
|
203
|
+
en: 'SINGAPORE',
|
|
204
|
+
es: 'SINGAPUR',
|
|
205
|
+
it: 'SINGAPORE'
|
|
206
|
+
},
|
|
207
|
+
'city.DELHI': {
|
|
208
|
+
en: 'DELHI',
|
|
209
|
+
es: 'DELHI',
|
|
210
|
+
it: 'DELHI'
|
|
211
|
+
},
|
|
212
|
+
'city.LAHORE': {
|
|
213
|
+
en: 'LAHORE',
|
|
214
|
+
es: 'LAHORE',
|
|
215
|
+
it: 'LAHORE'
|
|
216
|
+
},
|
|
217
|
+
'city.MOSCOW': {
|
|
218
|
+
en: 'MOSCOW',
|
|
219
|
+
es: 'MOSCÚ',
|
|
220
|
+
it: 'MOSCA'
|
|
221
|
+
},
|
|
222
|
+
'city.KYIV': {
|
|
223
|
+
en: 'KYIV',
|
|
224
|
+
es: 'KIEV',
|
|
225
|
+
it: 'KIEV'
|
|
226
|
+
},
|
|
227
|
+
'city.CAIRO': {
|
|
228
|
+
en: 'CAIRO',
|
|
229
|
+
es: 'EL CAIRO',
|
|
230
|
+
it: 'IL CAIRO'
|
|
231
|
+
},
|
|
232
|
+
'city.LAGOS': {
|
|
233
|
+
en: 'LAGOS',
|
|
234
|
+
es: 'LAGOS',
|
|
235
|
+
it: 'LAGOS'
|
|
236
|
+
},
|
|
237
|
+
'city.CAPE TOWN': {
|
|
238
|
+
en: 'CAPE TOWN',
|
|
239
|
+
es: 'CIUDAD DEL CABO',
|
|
240
|
+
it: 'CITTÀ DEL CAPO'
|
|
241
|
+
},
|
|
242
|
+
'city.NAIROBI': {
|
|
243
|
+
en: 'NAIROBI',
|
|
244
|
+
es: 'NAIROBI',
|
|
245
|
+
it: 'NAIROBI'
|
|
246
|
+
},
|
|
247
|
+
'city.CASABLANCA': {
|
|
248
|
+
en: 'CASABLANCA',
|
|
249
|
+
es: 'CASABLANCA',
|
|
250
|
+
it: 'CASABLANCA'
|
|
251
|
+
},
|
|
252
|
+
'region.North America': {
|
|
253
|
+
en: 'North America',
|
|
254
|
+
es: 'América del Norte',
|
|
255
|
+
it: 'Nord America'
|
|
256
|
+
},
|
|
257
|
+
'region.Europe': {
|
|
258
|
+
en: 'Europe',
|
|
259
|
+
es: 'Europa',
|
|
260
|
+
it: 'Europa'
|
|
261
|
+
},
|
|
262
|
+
'region.South America': {
|
|
263
|
+
en: 'South America',
|
|
264
|
+
es: 'América del Sur',
|
|
265
|
+
it: 'Sud America'
|
|
266
|
+
},
|
|
267
|
+
'region.Asia': {
|
|
268
|
+
en: 'Asia',
|
|
269
|
+
es: 'Asia',
|
|
270
|
+
it: 'Asia'
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
export function t(key, language, replacements) {
|
|
274
|
+
const translation = translations[key];
|
|
275
|
+
if (!translation) {
|
|
276
|
+
console.warn(`Translation missing for key: ${key}`);
|
|
277
|
+
return key;
|
|
278
|
+
}
|
|
279
|
+
let text = translation[language] || translation.en || key;
|
|
280
|
+
if (replacements) {
|
|
281
|
+
Object.entries(replacements).forEach(([placeholder, value]) => {
|
|
282
|
+
text = text.replace(`{${placeholder}}`, String(value));
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return text;
|
|
286
|
+
}
|
|
287
|
+
export const LANGUAGE_ROTATION = ['en', 'es', 'en', 'it'];
|
|
288
|
+
export function getNextLanguageIndex(currentIndex) {
|
|
289
|
+
return (currentIndex + 1) % LANGUAGE_ROTATION.length;
|
|
290
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type NewsItem } from '../components/slides/ArticleSlide.js';
|
|
2
|
+
import type { Segment } from './types.js';
|
|
3
|
+
import { type SupportedLanguage } from '../i18n.js';
|
|
4
|
+
export declare function fetchArticles(language?: SupportedLanguage): Promise<NewsItem[]>;
|
|
5
|
+
export declare function createArticlesSegment(articles: NewsItem[], onDataUpdate?: (nextArticles: NewsItem[]) => void, language?: SupportedLanguage): Segment;
|
|
6
|
+
export type { NewsItem };
|