From e2c3cd4779a714011d2a94edb123aef0e5e2e256 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Thu, 18 Aug 2022 08:21:55 +0300 Subject: [PATCH] feat: added rss feed for articles --- README.md | 1 + package-lock.json | 52 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/i18n/utils.ts | 34 +++++++++++++++++++---------- src/pages/rss.xml.ts | 35 +++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 src/pages/rss.xml.ts diff --git a/README.md b/README.md index 81aae62..f3fc16c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Personal website created with Astro. - [x] Multilang web-site - [x] Form handling via [Netlify Forms](https://www.netlify.com/products/forms/) - [x] Comment system via [utterances](https://utteranc.es/) +- [x] RSS Feed for articles - [ ] SEO optimized ## License diff --git a/package-lock.json b/package-lock.json index 7bea0c3..fa74583 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "yarkov.tech", "version": "0.0.1", "license": "MIT", + "dependencies": { + "@astrojs/rss": "^1.0.0" + }, "devDependencies": { "@astrojs/image": "^0.3.4", "@astrojs/mdx": "^0.10.0", @@ -183,6 +186,14 @@ "node": "^14.18.0 || >=16.12.0" } }, + "node_modules/@astrojs/rss": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-1.0.0.tgz", + "integrity": "sha512-vOPocf/SC0TfHsCwQXupvLGm0y3oIwpRCnnF2igDP3gFSxQEOk4nmsuVTEU2N87hYyX1/5uxbjJhixuSV/L8Cw==", + "dependencies": { + "fast-xml-parser": "^4.0.8" + } + }, "node_modules/@astrojs/solid-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@astrojs/solid-js/-/solid-js-1.0.0.tgz", @@ -3558,6 +3569,21 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.9.tgz", + "integrity": "sha512-4G8EzDg2Nb1Qurs3f7BpFV4+jpMVsdgLVuG1Uv8O2OHJfVCg7gcA53obuKbmVqzd4Y7YXVBK05oJG7hzGIdyzg==", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -8146,6 +8172,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -9335,6 +9366,14 @@ "prismjs": "^1.28.0" } }, + "@astrojs/rss": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-1.0.0.tgz", + "integrity": "sha512-vOPocf/SC0TfHsCwQXupvLGm0y3oIwpRCnnF2igDP3gFSxQEOk4nmsuVTEU2N87hYyX1/5uxbjJhixuSV/L8Cw==", + "requires": { + "fast-xml-parser": "^4.0.8" + } + }, "@astrojs/solid-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@astrojs/solid-js/-/solid-js-1.0.0.tgz", @@ -11761,6 +11800,14 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.9.tgz", + "integrity": "sha512-4G8EzDg2Nb1Qurs3f7BpFV4+jpMVsdgLVuG1Uv8O2OHJfVCg7gcA53obuKbmVqzd4Y7YXVBK05oJG7hzGIdyzg==", + "requires": { + "strnum": "^1.0.5" + } + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -14979,6 +15026,11 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", diff --git a/package.json b/package.json index c234b21..4a4ecab 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "devDependencies": { "@astrojs/image": "^0.3.4", "@astrojs/mdx": "^0.10.0", + "@astrojs/rss": "^1.0.0", "@astrojs/solid-js": "^1.0.0", "@types/mdx": "^2.0.2", "@typescript-eslint/eslint-plugin": "^5.33.1", diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts index f1f2566..4482df5 100644 --- a/src/i18n/utils.ts +++ b/src/i18n/utils.ts @@ -3,11 +3,10 @@ import * as utils from "@utils/url"; import { DEFAULT_LANGUAGE } from "@root/config"; import languages from "./languages"; -export function getTranslatedArticles(Astro: Readonly): MDXInstance[] { - const data = import.meta.glob>("../../content/articles/**/*.mdx", { eager: true }); - const modules: Record>> = {}; - const siteLang = utils.getLanguageFromURL(Astro.url.pathname); - const articles: MDXInstance[] = []; +type TranslatedArticlesType = Record> | Record>; + +export function formatArticlesByLangs(data: Record>): TranslatedArticlesType { + const modules: TranslatedArticlesType = {}; for (const module of Object.values(data)) { const splitted = module.file.split("/"); @@ -17,6 +16,15 @@ export function getTranslatedArticles(Astro: Readonly): MDXInstance modules[name][lang] = module; } + return modules; +} + +export function getTranslatedArticles(Astro: Readonly): MDXInstance[] { + const data = import.meta.glob>("../../content/articles/**/*.mdx", { eager: true }); + const modules = formatArticlesByLangs(data); + const siteLang = utils.getLanguageFromURL(Astro.url.pathname); + const articles: MDXInstance[] = []; + for (const key of Object.keys(modules)) { const langs = Object.keys(modules[key]); for (const articleLang of langs) { @@ -53,11 +61,13 @@ function transformExports(modules: Record) { const translations = transformExports(import.meta.glob("./*/ui.ts", { eager: true })); -export function useTranslation(Astro: Readonly): (key: UIDictionaryKeys) => string | undefined { - const lang = utils.getLanguageFromURL(Astro.url.pathname); - return function translate(key: UIDictionaryKeys): string | undefined { - const string: string | undefined = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key]; - if (string === undefined) console.log(`Cannot find any string for translation key "${key}" on "${Astro.url.pathname}" page.`); - return string; - }; +export function translate(key: UIDictionaryKeys, lang: LanguageKeys): string | undefined { + const string: string | undefined = translations[lang]?.[key] || translations[DEFAULT_LANGUAGE][key]; + if (string === undefined) console.log(`Cannot find any string for translation key "${key}".`); + return string; +} + +export function useTranslation(Astro: Readonly): (key: UIDictionaryKeys) => string | undefined { + const lang: LanguageKeys = utils.getLanguageFromURL(Astro.url.pathname) as LanguageKeys; + return (key: UIDictionaryKeys) => translate(key, lang); } diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts new file mode 100644 index 0000000..cdc152e --- /dev/null +++ b/src/pages/rss.xml.ts @@ -0,0 +1,35 @@ +import type { MDXInstance } from "astro"; + +import rss from "@astrojs/rss"; +import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/config"; +import * as utils from "@i18n/utils"; + +const data = import.meta.glob>("../../content/articles/**/*.mdx", { eager: true }); +const articles = Object.values(utils.formatArticlesByLangs(data)); +const items = []; + +for (const key of Object.keys(articles)) { + const article: Record> = articles[key]; + for (const lang of Object.keys(article)) { + const { title, description, slug, published_at, coverImage } = article[lang].frontmatter; + items.push({ + link: + `${import.meta.env.SITE}blog/` + + new Date(published_at).toLocaleDateString("en-CA") + + `${lang !== DEFAULT_LANGUAGE ? "/" + lang : ""}` + + `/${slug}/`, + title, + description: `${description}${coverImage !== undefined ? `${title}` : ""}`, + pubDate: published_at, + customData: `${lang}`, + }); + } +} + +export const get = () => + rss({ + title: SITE_METADATA.name, + description: utils.translate("site.description", "ru"), + site: import.meta.env.SITE, + items, + });