mirror of
https://github.com/fabrice404/olympics-calendar.git
synced 2026-01-01 19:11:05 +00:00
add other languages
This commit is contained in:
410
src/calendar.ts
Normal file
410
src/calendar.ts
Normal file
@ -0,0 +1,410 @@
|
||||
import Debug from "debug";
|
||||
import autoprefixer from "autoprefixer";
|
||||
import postcss from "postcss";
|
||||
import tailwindcss from "tailwindcss";
|
||||
|
||||
import { Event, NOC, Sport } from "./types";
|
||||
import { getAllSportsKeys, getSportIcon } from "./sports";
|
||||
import { existsSync, writeFileSync } from "fs";
|
||||
import { hasFile, readFile, saveFile } from "./io";
|
||||
import cheerio from "cheerio";
|
||||
import { getNOCFlag, getNOCName, isValidNOC } from "./nocs";
|
||||
import * as translate from "./translate";
|
||||
import { generateICS } from "./ics";
|
||||
|
||||
export class Calendar {
|
||||
private language: string;
|
||||
|
||||
private debug: Debug.Debugger;
|
||||
|
||||
private events: Event[] = [];
|
||||
private nocs: string[] = [];
|
||||
private sports: Sport[] = [];
|
||||
|
||||
constructor(language: string) {
|
||||
this.language = language;
|
||||
this.debug = Debug(`paris2024:calendar:${language}`);
|
||||
}
|
||||
|
||||
private addSport(sportKey: string, sportName: string) {
|
||||
if (!this.sports.find(sport => sport.key === sportKey)) {
|
||||
this.debug(`Adding sport: ${sportName} (${sportKey})`);
|
||||
this.sports.push({
|
||||
key: sportKey,
|
||||
name: sportName,
|
||||
NOCS: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private addNOC(noc: string) {
|
||||
if (!this.nocs.includes(noc)) {
|
||||
this.debug(`Adding NOC: ${noc}`);
|
||||
this.nocs.push(noc);
|
||||
}
|
||||
}
|
||||
|
||||
private addSportNOC(sportKey: string, sportName: string, noc: string) {
|
||||
this.addSport(sportKey, sportName);
|
||||
const sport = this.sports.find((sport) => sport.key === sportKey)!;
|
||||
if (!sport.NOCS.includes(noc)) {
|
||||
this.debug(`Adding NOC: ${noc} to sport: ${sportKey}`);
|
||||
sport.NOCS.push(noc);
|
||||
}
|
||||
};
|
||||
|
||||
public async generate() {
|
||||
this.debug(`Generating calendar for ${this.language}`);
|
||||
for (const sportKey of getAllSportsKeys()) {
|
||||
await this.getSportCalendar(sportKey);
|
||||
}
|
||||
this.genereateEventsCeremonies();
|
||||
this.generateCalendars();
|
||||
|
||||
this.generateMainPage();
|
||||
this.generateTodaysPage();
|
||||
this.generateCSS();
|
||||
}
|
||||
|
||||
private async getSportCalendar(sportKey: string) {
|
||||
const schedule = await this.downloadScheduleFromOfficialWebsite(sportKey);
|
||||
this.generateEventsFromSchedule(sportKey, schedule);
|
||||
}
|
||||
|
||||
private async downloadScheduleFromOfficialWebsite(sportKey: string) {
|
||||
this.debug(`Checking cache for schedule for ${sportKey}`);
|
||||
const cacheFile = `${__dirname}/../cache/${this.language}/${sportKey}.html`;
|
||||
|
||||
if (!hasFile(cacheFile)) {
|
||||
this.debug(`Downloading schedule for ${sportKey} in ${this.language}`);
|
||||
const response = await fetch(`https://olympics.com/${this.language}/paris-2024/schedule/${sportKey}`);
|
||||
const content = await response.text();
|
||||
saveFile(cacheFile, content);
|
||||
}
|
||||
|
||||
const html = readFile(cacheFile);
|
||||
const $ = cheerio.load(html);
|
||||
return JSON.parse($("#__NEXT_DATA__").text());
|
||||
}
|
||||
|
||||
private generateEventsFromSchedule(sportKey: string, data: any) {
|
||||
const sportName = data.query.pDisciplineLabel;
|
||||
const sportIcon = getSportIcon(sportKey);
|
||||
this.addSport(sportKey, sportName);
|
||||
|
||||
data.props.pageProps.scheduleDataSource.initialSchedule.units.forEach((unit: any) => {
|
||||
unit.startDateTimeUtc = new Date(unit.startDate).toISOString().replace(".000", "");
|
||||
unit.endDateTimeUtc = new Date(unit.endDate).toISOString().replace(".000", "");
|
||||
|
||||
const slugify = (text: string) => text.toLowerCase().replace(/\s/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "")
|
||||
.replace(/-+/g, "-");
|
||||
|
||||
const event: Event = {
|
||||
UID: `${unit.startDateTimeUtc.replace(/[:-]/g, "")}-${sportKey}-${slugify(unit.eventUnitName).toUpperCase()}`,
|
||||
DTSTAMP: unit.startDateTimeUtc.replace(/[:-]/g, ""),
|
||||
DTSTART: unit.startDateTimeUtc.replace(/[:-]/g, ""),
|
||||
DTEND: unit.endDateTimeUtc.replace(/[:-]/g, ""),
|
||||
DESCRIPTION: `${sportName} - ${unit.eventUnitName}`,
|
||||
SUMMARY: `${sportIcon} ${unit.eventUnitName}`.trim(),
|
||||
LOCATION: unit.venueDescription,
|
||||
_SPORT: sportKey,
|
||||
_NOCS: [],
|
||||
_COMPETITORS: [],
|
||||
_UNITNAME: unit.eventUnitName,
|
||||
_MEDAL: !!unit.medalFlag,
|
||||
_GENDER: unit.genderCode,
|
||||
};
|
||||
|
||||
if (unit.competitors) {
|
||||
const competitors = unit.competitors
|
||||
.filter((competitor: any) => competitor.noc && isValidNOC(competitor.noc))
|
||||
.sort((a: any, b: any) => a.order > b.order ? 1 : -1);
|
||||
event._NOCS = competitors.map((competitor: any) => {
|
||||
this.addSportNOC(sportKey, sportName, competitor.noc);
|
||||
this.addNOC(competitor.noc);
|
||||
return competitor.noc;
|
||||
});
|
||||
|
||||
// two competitors, we put them in the summary
|
||||
if (competitors.length === 2) {
|
||||
const competitor1 = competitors.shift();
|
||||
const competitor2 = competitors.shift();
|
||||
|
||||
event.UID += `-${competitor1.noc}-${competitor2.noc}`;
|
||||
if (competitor1.name !== getNOCName(competitor1.noc)) {
|
||||
event.SUMMARY = `${sportIcon} ${competitor1.name} ${getNOCFlag(competitor1.noc)} - ${getNOCFlag(competitor2.noc)} ${competitor2.name}`;
|
||||
} else {
|
||||
event.SUMMARY = `${sportIcon} ${competitor1.noc} ${getNOCFlag(competitor1.noc)} - ${getNOCFlag(competitor2.noc)} ${competitor2.noc}`;
|
||||
}
|
||||
} else if (competitors.length !== 0) {
|
||||
// more than two, we put them in the description
|
||||
competitors
|
||||
.sort((a: any, b: any) => a.name > b.name ? 1 : -1)
|
||||
.forEach((competitor: any) => {
|
||||
if (competitor.name !== getNOCName(competitor.noc)) {
|
||||
event.DESCRIPTION += `\\n${getNOCFlag(competitor.noc)} ${competitor.name}`;
|
||||
event._COMPETITORS.push({ noc: competitor.noc, name: `${getNOCFlag(competitor.noc)} ${competitor.name}` });
|
||||
} else {
|
||||
event.DESCRIPTION += `\\n${getNOCFlag(competitor.noc)} ${competitor.noc}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.events.push(event);
|
||||
});
|
||||
}
|
||||
|
||||
private genereateEventsCeremonies() {
|
||||
let startDateUtc = new Date("2024-07-26T17:30:00Z").toISOString().replace(".000", "");
|
||||
let endDateUtc = new Date("2024-07-26T21:00:00Z").toISOString().replace(".000", "");
|
||||
|
||||
const opening: Event = {
|
||||
UID: `${startDateUtc.replace(/[:-]/g, "")}-opening-ceremony`,
|
||||
DTSTAMP: startDateUtc.replace(/[:-]/g, ""),
|
||||
DTSTART: startDateUtc.replace(/[:-]/g, ""),
|
||||
DTEND: endDateUtc.replace(/[:-]/g, ""),
|
||||
DESCRIPTION: `Paris 2024 - ${translate.openingCeremony.get(this.language)}`,
|
||||
SUMMARY: `Paris 2024 - ${translate.openingCeremony.get(this.language)}`,
|
||||
LOCATION: "Paris",
|
||||
_COMPETITORS: [],
|
||||
_GENDER: "",
|
||||
_MEDAL: false,
|
||||
_NOCS: this.nocs,
|
||||
_SPORT: "",
|
||||
_UNITNAME: "",
|
||||
};
|
||||
this.events.push(opening);
|
||||
|
||||
startDateUtc = new Date("2024-08-11T19:00:00Z").toISOString().replace(".000", "");
|
||||
endDateUtc = new Date("2024-08-11T21:15:00Z").toISOString().replace(".000", "");
|
||||
|
||||
const closing: Event = {
|
||||
UID: `${startDateUtc.replace(/[:-]/g, "")}-closing-ceremony`,
|
||||
DTSTAMP: startDateUtc.replace(/[:-]/g, ""),
|
||||
DTSTART: startDateUtc.replace(/[:-]/g, ""),
|
||||
DTEND: endDateUtc.replace(/[:-]/g, ""),
|
||||
DESCRIPTION: "Paris 2024 - Closing ceremony",
|
||||
SUMMARY: "Paris 2024 - Closing ceremony",
|
||||
LOCATION: "Stade de France, Saint-Denis",
|
||||
_COMPETITORS: [],
|
||||
_GENDER: "",
|
||||
_MEDAL: false,
|
||||
_NOCS: this.nocs,
|
||||
_SPORT: "",
|
||||
_UNITNAME: "",
|
||||
};
|
||||
this.events.push(closing);
|
||||
}
|
||||
|
||||
private getKey(sportKey: string, noc: string) {
|
||||
if (this.language === "en") {
|
||||
return `${sportKey}/${noc}`;
|
||||
}
|
||||
return `${sportKey}/${this.language}/${noc}`;
|
||||
}
|
||||
|
||||
private generateCalendars() {
|
||||
// sports
|
||||
for (const sport of this.sports) {
|
||||
// sport/general
|
||||
let events = this.events
|
||||
.filter((event) => event._SPORT === sport.key)
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
let key = this.getKey(sport.key, "general");
|
||||
let title = `${getSportIcon(sport.key)} ${sport.name} | Paris 2024`;
|
||||
if (events.length > 0) {
|
||||
generateICS(title, key, events);
|
||||
}
|
||||
|
||||
// sport/medals
|
||||
events = this.events
|
||||
.filter((event) => event._SPORT === sport.key && event._MEDAL)
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
key = this.getKey(sport.key, "medals");
|
||||
title = `${getSportIcon(sport.key)} ${sport.name} 🏅 | Paris 2024`;
|
||||
if (events.length > 0) {
|
||||
generateICS(title, key, events);
|
||||
}
|
||||
|
||||
// sport/noc
|
||||
for (const noc of sport.NOCS) {
|
||||
events = this.events
|
||||
.filter((event) => event._SPORT === sport.key && event._NOCS.includes(noc))
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
key = this.getKey(sport.key, noc);
|
||||
title = `${getNOCFlag(noc)} ${getNOCName(noc)} ${sport.name} | Paris 2024`;
|
||||
if (events.length > 0) {
|
||||
generateICS(title, key, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nocs
|
||||
for (const noc of this.nocs) {
|
||||
// general/noc
|
||||
let events = this.events
|
||||
.filter((event) => event._NOCS.includes(noc))
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
let key = this.getKey("general", noc);
|
||||
let title = `${getNOCFlag(noc)} ${getNOCName(noc)} | Paris 2024`;
|
||||
if (events.length > 0) {
|
||||
generateICS(title, key, events);
|
||||
}
|
||||
|
||||
// medals/noc
|
||||
events = this.events
|
||||
.filter((event) => event._NOCS.includes(noc) && event._MEDAL)
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
key = this.getKey("medals", noc);
|
||||
title = `${getNOCFlag(noc)} ${getNOCName(noc)} 🏅 | Paris 2024`
|
||||
if (events.length > 0) {
|
||||
generateICS(title, key, events);
|
||||
}
|
||||
}
|
||||
|
||||
// general/general
|
||||
const events = this.events
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
const key = this.getKey("general", "general");
|
||||
const title = `Paris 2024`;
|
||||
if (events.length > 0) {
|
||||
generateICS(title, key, events);
|
||||
}
|
||||
|
||||
// medals/general
|
||||
const medals = this.events
|
||||
.filter((event) => event._MEDAL)
|
||||
.sort((a, b) => a.UID > b.UID ? 1 : -1);
|
||||
const medalsKey = this.getKey("medals", "general");
|
||||
const medalsTitle = `🏅 Paris 2024`;
|
||||
if (medals.length > 0) {
|
||||
generateICS(medalsTitle, medalsKey, medals);
|
||||
}
|
||||
}
|
||||
|
||||
private generateMainPage() {
|
||||
const accordionClass = "collapse collapse-arrow bg-gray-100 mb-1"
|
||||
const buttonClass = "btn btn-sm bg-gray-300 min-w-24 mb-1";
|
||||
|
||||
const calendars: string[] = [];
|
||||
const todays: string[] = [];
|
||||
|
||||
calendars.push(`<div class="${accordionClass}">`);
|
||||
calendars.push(` <input type="radio" name="accordion" checked="checked">`);
|
||||
calendars.push(` <div class="collapse-title text-xl font-medium">${translate.allSports.get(this.language)}</div>`);
|
||||
calendars.push(` <div class="collapse-content text-center">`)
|
||||
calendars.push(` <div>`);
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('general/general', '${this.language}');">${translate.fullSchedule.get(this.language)}</button>`);
|
||||
calendars.push(` </div>`);
|
||||
for (const noc of this.nocs.sort()) {
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('general/${noc}', '${this.language}');">${getNOCFlag(noc)} ${noc}</button>`);
|
||||
}
|
||||
calendars.push(` </div>`);
|
||||
calendars.push(`</div>`);
|
||||
|
||||
calendars.push(`<div class="${accordionClass}">`);
|
||||
calendars.push(` <input type="radio" name="accordion">`);
|
||||
calendars.push(` <div class="collapse-title text-xl font-medium">🏅 ${translate.medalEvents.get(this.language)}</div>`);
|
||||
calendars.push(` <div class="collapse-content text-center">`)
|
||||
calendars.push(` <div>`);
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('medals/general', '${this.language}');">${translate.fullSchedule.get(this.language)}</button>`);
|
||||
calendars.push(` </div>`);
|
||||
for (const noc of this.nocs.sort()) {
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('medals/${noc}', '${this.language}');">${getNOCFlag(noc)} ${noc}</button>`);
|
||||
}
|
||||
calendars.push(` </div>`);
|
||||
calendars.push(`</div>`);
|
||||
|
||||
for (const sport of this.sports.sort()) {
|
||||
calendars.push(`<div class="${accordionClass}">`);
|
||||
calendars.push(` <input type="radio" name="accordion">`);
|
||||
calendars.push(` <div class="collapse-title text-xl font-medium">${getSportIcon(sport.key)} ${sport.name}</div>`);
|
||||
calendars.push(` <div class="collapse-content text-center">`)
|
||||
calendars.push(` <div>`);
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('${sport.key}/general', '${this.language}');">${translate.fullSchedule.get(this.language)}</button>`);
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('${sport.key}/medals', '${this.language}');">🏅 ${translate.medalEvents.get(this.language)}</button>`);
|
||||
calendars.push(` </div>`);
|
||||
for (const noc of sport.NOCS.sort()) {
|
||||
calendars.push(` <button class="${buttonClass}" onclick="showModal('${sport.key}/${noc}', '${this.language}');">${getNOCFlag(noc)} ${noc}</button>`);
|
||||
}
|
||||
calendars.push(` </div>`);
|
||||
calendars.push(`</div>`);
|
||||
}
|
||||
|
||||
const template = readFile(`${__dirname}/index/template.html`);
|
||||
const output = template
|
||||
.replace("{{calendars}}", calendars.join("\r\n"))
|
||||
.replace("{{todays}}", [todays].join("\r\n"));
|
||||
saveFile(
|
||||
this.language === "en" ?
|
||||
"docs/index.html" :
|
||||
`docs/${this.language}/index.html`,
|
||||
output);
|
||||
}
|
||||
|
||||
private generateTodaysPage() {
|
||||
const content: string[] = [];
|
||||
|
||||
for (const event of this.events) {
|
||||
let sport = this.sports.find((sport) => sport.key === event._SPORT);
|
||||
if (!sport) {
|
||||
sport = {
|
||||
name: "Ceremony",
|
||||
key: "",
|
||||
NOCS: [],
|
||||
};
|
||||
}
|
||||
const summary = event.SUMMARY.match(/ceremony/gi) ? event.SUMMARY : event.SUMMARY.split(" ").slice(1).join(" ");
|
||||
|
||||
content.push(`<div class="event py-4" data-start="${event.DTSTART}" data-end="${event.DTEND}" data-noc="${event._NOCS.join(",")}">`);
|
||||
content.push(" <div class=\"time w-1/4 align-top text-right inline-block text-5xl text-center tabular-nums pr-2 border-r border-slate-900/10\">__:__</div>");
|
||||
content.push(" <div class=\"w-3/5 align-top inline-block text-black pl-2\">");
|
||||
content.push(" <div class=\"text-2xl\">");
|
||||
content.push(` ${event._MEDAL ? "🏅" : ""}`);
|
||||
content.push(` ${sport.name.toUpperCase()}`);
|
||||
if (event._GENDER === "M") {
|
||||
content.push(" <span class=\"text-xs align-middle bg-blue-400 text-white py-1 px-2 rounded-xl\">M</span>");
|
||||
} else if (event._GENDER === "W") {
|
||||
content.push(" <span class=\"text-xs align-middle bg-pink-400 text-white py-1 px-2 rounded-xl\">W</span>");
|
||||
}
|
||||
content.push(" </div>");
|
||||
if (event._UNITNAME.match(summary)) {
|
||||
content.push(` <div class="">${summary}`);
|
||||
} else {
|
||||
content.push(` <div class="">${event._UNITNAME}`);
|
||||
content.push(` <div class="">${summary}</div>`);
|
||||
}
|
||||
if (event._COMPETITORS) {
|
||||
event._COMPETITORS.forEach((competitor) => {
|
||||
content.push(`<div class= "competitor ${competitor.noc}"> ${competitor.name} </div>`);
|
||||
});
|
||||
}
|
||||
content.push(" </div>");
|
||||
content.push(" </div>");
|
||||
content.push("</div>");
|
||||
|
||||
}
|
||||
|
||||
const template = readFile(`${__dirname}/today/template.html`);
|
||||
const output = template
|
||||
.replace("{{events}}", content.join("\r\n"));
|
||||
saveFile(
|
||||
this.language === "en" ?
|
||||
"docs/today.html" :
|
||||
`docs/${this.language}/today.html`,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
private generateCSS() {
|
||||
postcss([autoprefixer, tailwindcss])
|
||||
.process(readFile(`${__dirname}/index/template.css`), { from: "index/template.css", to: "docs/style.css" })
|
||||
.then((result) => {
|
||||
saveFile("docs/style.css", result.css);
|
||||
});
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
116
src/index.ts
116
src/index.ts
@ -11,6 +11,7 @@ import { Event, Sport } from "./types";
|
||||
import { getSportIcon } from "./sports";
|
||||
import { isValidNOC, getNOCName, getNOCFlag } from "./nocs";
|
||||
import { generateICS } from "./ics";
|
||||
import { Calendar } from "./calendar";
|
||||
|
||||
const debug = Debug("paris2024:index");
|
||||
|
||||
@ -358,61 +359,66 @@ const generateCSS = () => {
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
"3x3-basketball",
|
||||
"archery",
|
||||
"artistic-gymnastics",
|
||||
"artistic-swimming",
|
||||
"athletics",
|
||||
"badminton",
|
||||
"basketball",
|
||||
"beach-volleyball",
|
||||
"boxing",
|
||||
"breaking",
|
||||
"canoe-slalom",
|
||||
"canoe-sprint",
|
||||
"cycling-bmx-freestyle",
|
||||
"cycling-bmx-racing",
|
||||
"cycling-mountain-bike",
|
||||
"cycling-road",
|
||||
"cycling-track",
|
||||
"diving",
|
||||
"equestrian",
|
||||
"fencing",
|
||||
"football",
|
||||
"golf",
|
||||
"handball",
|
||||
"hockey",
|
||||
"judo",
|
||||
"marathon-swimming",
|
||||
"modern-pentathlon",
|
||||
"rhythmic-gymnastics",
|
||||
"rowing",
|
||||
"rugby-sevens",
|
||||
"sailing",
|
||||
"shooting",
|
||||
"skateboarding",
|
||||
"sport-climbing",
|
||||
"surfing",
|
||||
"swimming",
|
||||
"table-tennis",
|
||||
"taekwondo",
|
||||
"tennis",
|
||||
"trampoline-gymnastics",
|
||||
"triathlon",
|
||||
"volleyball",
|
||||
"water-polo",
|
||||
"weightlifting",
|
||||
"wrestling",
|
||||
]
|
||||
.map((key) => extractSportCalendar(key)),
|
||||
);
|
||||
generateCeremoniesEvents();
|
||||
generateCalendars();
|
||||
generateOutputPage();
|
||||
generateTodayPage();
|
||||
generateCSS();
|
||||
// await Promise.all(
|
||||
// [
|
||||
// "3x3-basketball",
|
||||
// "archery",
|
||||
// "artistic-gymnastics",
|
||||
// "artistic-swimming",
|
||||
// "athletics",
|
||||
// "badminton",
|
||||
// "basketball",
|
||||
// "beach-volleyball",
|
||||
// "boxing",
|
||||
// "breaking",
|
||||
// "canoe-slalom",
|
||||
// "canoe-sprint",
|
||||
// "cycling-bmx-freestyle",
|
||||
// "cycling-bmx-racing",
|
||||
// "cycling-mountain-bike",
|
||||
// "cycling-road",
|
||||
// "cycling-track",
|
||||
// "diving",
|
||||
// "equestrian",
|
||||
// "fencing",
|
||||
// "football",
|
||||
// "golf",
|
||||
// "handball",
|
||||
// "hockey",
|
||||
// "judo",
|
||||
// "marathon-swimming",
|
||||
// "modern-pentathlon",
|
||||
// "rhythmic-gymnastics",
|
||||
// "rowing",
|
||||
// "rugby-sevens",
|
||||
// "sailing",
|
||||
// "shooting",
|
||||
// "skateboarding",
|
||||
// "sport-climbing",
|
||||
// "surfing",
|
||||
// "swimming",
|
||||
// "table-tennis",
|
||||
// "taekwondo",
|
||||
// "tennis",
|
||||
// "trampoline-gymnastics",
|
||||
// "triathlon",
|
||||
// "volleyball",
|
||||
// "water-polo",
|
||||
// "weightlifting",
|
||||
// "wrestling",
|
||||
// ]
|
||||
// .map((key) => extractSportCalendar(key)),
|
||||
// );
|
||||
// generateCeremoniesEvents();
|
||||
// generateCalendars();
|
||||
// generateOutputPage();
|
||||
// generateTodayPage();
|
||||
// generateCSS();
|
||||
|
||||
for (const language of ["en", "ja", "ko", "ru", "zh"]) {
|
||||
const cal = new Calendar(language)
|
||||
await cal.generate();
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html data-theme="cmyk">
|
||||
|
||||
<head>
|
||||
<title>Paris 2024 Summer Olympic Games calendars</title>
|
||||
<link href="./style.css?refresh=20240827" rel="stylesheet">
|
||||
<link href="https://github.com/fabrice404/olympics-calendar/style.css?refresh=20240730" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Paris 2024 Summer Olympic Games calendars">
|
||||
<meta name="keywords" content="Paris 2024, Summer Olympic Games, calendars">
|
||||
@ -12,36 +12,47 @@
|
||||
|
||||
<body>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center justify-between border-b pb-4 border-slate-900/10">
|
||||
<h1 class="text-4xl">Paris 2024 Summer Olympic Games calendars</h1>
|
||||
|
||||
<div>
|
||||
<a href="https://github.com/fabrice404/olympics-calendar" target="_blank"
|
||||
class="ml-6 block text-slate-200 hover:text-sky-500 w-10 h-10">
|
||||
<svg viewBox="0 0 16 16" class="w-10 h-10" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z">
|
||||
</path>
|
||||
</svg></a>
|
||||
<div class="navbar bg-base-100">
|
||||
<div class="navbar-start">
|
||||
<div class="dropdown">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow">
|
||||
<li><a href="https://fabrice404.github.io/olympics-calendar/index.html">English</a></li>
|
||||
<li><a href="https://fabrice404.github.io/olympics-calendar/ja/index.html">日本語</a></li>
|
||||
<li><a href="https://fabrice404.github.io/olympics-calendar/ko/index.html">한국어</a></li>
|
||||
<li><a href="https://fabrice404.github.io/olympics-calendar/ru/index.html">Русский</a></li>
|
||||
<li><a href="https://fabrice404.github.io/olympics-calendar/zh/index.html">中文</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-center">
|
||||
<a class="btn btn-ghost text-xl" href="./">Paris 2024 calendars</a>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl pb-4 pt-8">How to add a calendar to your Google calendar</h2>
|
||||
<ol class="list-decimal ml-10">
|
||||
<li>Right click on a calendar and select "Copy link address"</li>
|
||||
<li>Open your Google calendar</li>
|
||||
<li>Click the plus sign next to “Other Calendars” on the left menu</li>
|
||||
<li>Click "From URL"</li>
|
||||
<li>Paste your calendar URL</li>
|
||||
<li>Click "Add calendar"</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{calendars}}
|
||||
</div>
|
||||
|
||||
<dialog id="modal" class="modal">
|
||||
<div class="modal-box">
|
||||
</h3>
|
||||
<input type="text" class="input input-bordered w-full text-sm" id="link"></input>
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">Close</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<div class="text-sm my-10">
|
||||
<h2 class="text-3xl pb-4 pt-8">View today's events by NOC</h2>
|
||||
{{todays}}
|
||||
@ -52,6 +63,13 @@
|
||||
All trademarks, logos and brand names are the property of their respective owners.
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const showModal = (key, language) => {
|
||||
document.querySelector("#modal #link")
|
||||
.setAttribute('value', `https://fabrice404.github.io/olympics-calendar/${key}.ics`);
|
||||
modal.showModal();
|
||||
};
|
||||
</script>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-0KQC1F1K4H"></script>
|
||||
<script>
|
||||
|
||||
11
src/io.ts
Normal file
11
src/io.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
|
||||
export const saveFile = (path: string, content: string): void => {
|
||||
const folder = path.split("/").slice(0, -1).join("/");
|
||||
mkdirSync(folder, { recursive: true });
|
||||
writeFileSync(path, content);
|
||||
};
|
||||
|
||||
export const hasFile = (path: string) => existsSync(path);
|
||||
|
||||
export const readFile = (path: string) => readFileSync(path, "utf-8");
|
||||
@ -53,3 +53,5 @@ export const getSportIcon = (sport: string): string => {
|
||||
console.error(`No icon set for ${sport}`);
|
||||
return "";
|
||||
};
|
||||
|
||||
export const getAllSportsKeys = (): string[] => [...SPORTS.keys()];
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>Paris 2024 Summer Olympic Games - Today's events</title>
|
||||
<link href="./style.css?refresh=20240827" rel="stylesheet">
|
||||
<link href="https://github.com/fabrice404/olympics-calendar/style.css?refresh=20240730" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Paris 2024 Summer Olympic Games - Today's events">
|
||||
<meta name="keywords" content="Paris 2024, Summer Olympic Games - Today's events">
|
||||
|
||||
56
src/translate.ts
Normal file
56
src/translate.ts
Normal file
@ -0,0 +1,56 @@
|
||||
export const allSports = new Map<string, string>([
|
||||
["en", "All sports"],
|
||||
["ja", "すべてのスポーツ"],
|
||||
["ko", "모든 스포츠"],
|
||||
["ru", "Все виды спорта"],
|
||||
["zh", "所有运动"],
|
||||
|
||||
]);
|
||||
|
||||
export const fullSchedule = new Map<string, string>([
|
||||
["en", "Full schedule"],
|
||||
["ja", "フルスケジュール"],
|
||||
["ko", "전체 일정"],
|
||||
["ru", "Полное расписание"],
|
||||
["zh", "完整时间表"],
|
||||
]);
|
||||
|
||||
export const medalEvents = new Map<string, string>([
|
||||
["en", "Medal events"],
|
||||
["ja", "メダルイベント"],
|
||||
["ko", "메달 이벤트"],
|
||||
["ru", "Медальные события"],
|
||||
["zh", "奖牌赛事"],
|
||||
]);
|
||||
|
||||
export const genderMen = new Map<string, string>([
|
||||
["en", "M"],
|
||||
["ja", "男性"],
|
||||
["ko", "남성"],
|
||||
["ru", "М"],
|
||||
["zh", "男"],
|
||||
]);
|
||||
|
||||
export const genderWomen = new Map<string, string>([
|
||||
["en", "W"],
|
||||
["ja", "女性"],
|
||||
["ko", "여성"],
|
||||
["ru", "Ж"],
|
||||
["zh", "女"],
|
||||
]);
|
||||
|
||||
export const openingCeremony = new Map<string, string>([
|
||||
["en", "Opening Ceremony"],
|
||||
["ja", "開会式"],
|
||||
["ko", "개막식"],
|
||||
["ru", "Церемония открытия"],
|
||||
["zh", "开幕式"],
|
||||
]);
|
||||
|
||||
export const closingCeremony = new Map<string, string>([
|
||||
["en", "Closing Ceremony"],
|
||||
["ja", "閉会式"],
|
||||
["ko", "폐막식"],
|
||||
["ru", "Церемония закрытия"],
|
||||
["zh", "闭幕式"],
|
||||
]);
|
||||
Reference in New Issue
Block a user