Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"prepare": "husky",
"check": "astro check",
"generate:og": "node scripts/generate-og.mjs",
"generate:data": "node scripts/update-data.mjs --lang=tr && node scripts/update-data.mjs --lang=en"
"generate:data": "node scripts/update-data.mjs"
},
"dependencies": {
"@astrojs/partytown": "^2.1.5",
Expand Down
36 changes: 9 additions & 27 deletions scripts/generate-og.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Resvg } from '@resvg/resvg-js'
import satori from 'satori'
import fs from 'fs'
import path from 'path'
import { SUPPORTED_LANGS } from '../src/config.mjs'

const fontRegular = await fetch(
'https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZg.ttf',
Expand All @@ -16,27 +17,7 @@ const logoDark = fs.readFileSync(path.join(repoRoot, 'src/assets/logo-dark.png')

const DEFAULT_COLORS = { bg: '#18181b', text: '#e5e7eb' }

const LANG_TEXTS = {
tr: {
explore: 'Makaleleri Keşfet',
category: 'KATEGORİ',
article: 'MAKALE',
tagline: 'Sinir ağları, transformerlar, pekiştirmeli öğrenme ve daha fazlası',
description: 'Makine öğrenimi kavramlarının net açıklamaları',
suffix: 'ML Kavramları Açıklandı',
},
en: {
explore: 'Explore Articles',
category: 'CATEGORY',
article: 'ARTICLE',
tagline: 'Neural networks, transformers, reinforcement learning & more',
description: 'Clear explanations of machine learning concepts',
suffix: 'ML Concepts Explained',
},
}

async function makeSvg(title, color, isArticle, lang = 'en', logo) {
const texts = LANG_TEXTS[lang] || LANG_TEXTS.en
async function makeSvg(title, color, isArticle, texts, logo) {
const bg = color || DEFAULT_COLORS.bg
const svg = await satori(
{
Expand Down Expand Up @@ -177,29 +158,30 @@ async function makeSvg(title, color, isArticle, lang = 'en', logo) {
const outDir = path.join(repoRoot, 'public', 'og')
fs.mkdirSync(outDir, { recursive: true })

const SUPPORTED_LANGS = ['tr', 'en']

for (const lang of SUPPORTED_LANGS) {
const langOutDir = path.join(outDir, lang)
fs.mkdirSync(langOutDir, { recursive: true })

const dataDir = path.join(repoRoot, 'src', 'data', lang)
let categories = []
let articles = []
let categories
let articles
let langTexts

try {
categories = JSON.parse(fs.readFileSync(path.join(dataDir, 'categories.json'), 'utf8'))
articles = JSON.parse(fs.readFileSync(path.join(dataDir, 'articles.json'), 'utf8'))
langTexts = JSON.parse(fs.readFileSync(path.join(dataDir, 'og-labels.json'), 'utf8'))
} catch {
console.log(`No data found for language: ${lang}`)
continue
}

const categoryColorMap = {}
for (const cat of categories) {
categoryColorMap[cat.name] = cat.color || null
}

const svgIndex = await makeSvg('4byte.dev', null, false, lang, logoDark)
const svgIndex = await makeSvg('4byte.dev', null, false, langTexts, logoDark)
const resvgIndex = new Resvg(svgIndex, { fitTo: { mode: 'width', value: 1200 } })
fs.writeFileSync(path.join(langOutDir, 'index.png'), Buffer.from(resvgIndex.render().asPng()))
console.log(`Generated public/og/${lang}/index.png`)
Expand All @@ -209,7 +191,7 @@ for (const lang of SUPPORTED_LANGS) {
} else {
for (const { slug, title, category } of articles) {
const color = categoryColorMap[category] || null
const svgArticle = await makeSvg(title, color, true, lang, logoDark)
const svgArticle = await makeSvg(title, color, true, langTexts, logoDark)
const resvgArticle = new Resvg(svgArticle, { fitTo: { mode: 'width', value: 1200 } })
fs.writeFileSync(path.join(langOutDir, `${slug}.png`), Buffer.from(resvgArticle.render().asPng()))
console.log(`Generated public/og/${lang}/${slug}.png`)
Expand Down
205 changes: 100 additions & 105 deletions scripts/update-data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,123 +3,118 @@ import { promises as fs } from 'fs'
import path from 'path'
import matter from 'gray-matter'
import slugify from 'slugify'
import { SUPPORTED_LANGS } from '../src/config.mjs'

const readJSON = async (file) => JSON.parse(await fs.readFile(file, 'utf8'))
const writeJSON = async (file, data) => await fs.writeFile(file, JSON.stringify(data, null, 2), 'utf8')

const langArg = process.argv.find((arg) => arg.startsWith('--lang='))
const targetLang = langArg ? langArg.split('=')[1] : 'tr'

const SUPPORTED_LANGS = ['tr', 'en']
if (!SUPPORTED_LANGS.includes(targetLang)) {
console.error(`Unsupported language: ${targetLang}. Supported: ${SUPPORTED_LANGS.join(', ')}`)
process.exit(1)
}
const writeJSON = async (file, data) => await fs.writeFile(file, JSON.stringify(data, null, 2) + '\n', 'utf8')

;(async () => {
const __dirname = new URL('.', import.meta.url).pathname
const repoRoot = path.resolve(__dirname, '..')
const contentDir = path.join(repoRoot, 'src/content/articles', targetLang)
const tagsPath = path.join(repoRoot, 'src/data', targetLang, 'tags.json')
const categoriesPath = path.join(repoRoot, 'src/data', targetLang, 'categories.json')
const articlesPath = path.join(repoRoot, 'src/data', targetLang, 'articles.json')

const tags = (await readJSON(tagsPath)) ?? []
const categories = (await readJSON(categoriesPath)) ?? []

tags.forEach((t) => (t.articleCount = 0))
categories.forEach((c) => (c.articleCount = 0))

const addedTags = new Set()
const addedCategories = new Set()

let mdFiles = []
try {
mdFiles = (await fs.readdir(contentDir)).filter((f) => f.endsWith('.md'))
} catch {
console.log(`No content directory for language '${targetLang}': ${contentDir}`)
console.log('Skipping article generation.')
}

const articles = []

for (const file of mdFiles) {
const filePath = path.join(contentDir, file)
const raw = await fs.readFile(filePath, 'utf8')
const { data } = matter(raw)

const title = data.title?.trim() ?? path.parse(file).name
const slug = data.slug?.trim() ?? slugify(title, { lower: true })

const fileTags = Array.isArray(data.tags) ? data.tags : []
for (const tagName of fileTags) {
const existing = tags.find((t) => t.name === tagName)
if (existing) {
existing.articleCount = (existing.articleCount ?? 0) + 1
} else {
tags.push({
id: String(tags.length + 1),
name: tagName,
slug: slugify(tagName, { lower: true }),
articleCount: 1,
description: '',
})
addedTags.add(tagName)
}
}
for (const lang of SUPPORTED_LANGS) {
const contentDir = path.join(repoRoot, 'src/content/articles', lang)
const tagsPath = path.join(repoRoot, 'src/data', lang, 'tags.json')
const categoriesPath = path.join(repoRoot, 'src/data', lang, 'categories.json')
const articlesPath = path.join(repoRoot, 'src/data', lang, 'articles.json')

const catName = data.category?.trim()
if (catName) {
const existing = categories.find((c) => c.name === catName)
if (existing) {
existing.articleCount = (existing.articleCount ?? 0) + 1
} else {
categories.push({
id: String(categories.length + 1),
name: catName,
slug: slugify(catName, { lower: true }),
description: '',
color: '',
articleCount: 1,
})
addedCategories.add(catName)
}
}
const tags = (await readJSON(tagsPath)) ?? []
const categories = (await readJSON(categoriesPath)) ?? []

articles.push({ title, slug, category: data.category?.trim() ?? '' })
}
tags.forEach((t) => (t.articleCount = 0))
categories.forEach((c) => (c.articleCount = 0))

const filteredTags = tags.filter((t) => (t.articleCount ?? 0) > 0)
const filteredCategories = categories.filter((c) => (c.articleCount ?? 0) > 0)

await writeJSON(tagsPath, filteredTags)
await writeJSON(categoriesPath, filteredCategories)
await writeJSON(articlesPath, articles)

console.log(`Updated data for language: ${targetLang}`)
console.log(` Articles: ${articles.length}`)
console.log(` Categories: ${filteredCategories.length}`)
console.log(` Tags: ${filteredTags.length}`)

if (process.env.GITHUB_EVENT_PATH) {
const event = JSON.parse(await fs.readFile(process.env.GITHUB_EVENT_PATH, 'utf8'))
const prNumber = event.pull_request?.number
if (prNumber) {
const lines = []
if (addedTags.size) {
lines.push(`## New tags added for ${targetLang} (please add descriptions)`)
for (const t of addedTags) lines.push(`- \`${t}\``)
lines.push('')
const addedTags = new Set()
const addedCategories = new Set()

let mdFiles = []
try {
mdFiles = (await fs.readdir(contentDir)).filter((f) => f.endsWith('.md'))
} catch {
console.log(`No content directory for language '${lang}': ${contentDir}`)
console.log('Skipping article generation.')
}

const articles = []

for (const file of mdFiles) {
const filePath = path.join(contentDir, file)
const raw = await fs.readFile(filePath, 'utf8')
const { data } = matter(raw)

const title = data.title?.trim() ?? path.parse(file).name
const slug = data.slug?.trim() ?? slugify(title, { lower: true })

const fileTags = Array.isArray(data.tags) ? data.tags : []
for (const tagName of fileTags) {
const existing = tags.find((t) => t.name === tagName)
if (existing) {
existing.articleCount = (existing.articleCount ?? 0) + 1
} else {
tags.push({
id: String(tags.length + 1),
name: tagName,
slug: slugify(tagName, { lower: true }),
articleCount: 1,
description: '',
})
addedTags.add(tagName)
}
}
if (addedCategories.size) {
lines.push(`## New categories added for ${targetLang} (please add descriptions)`)
for (const c of addedCategories) lines.push(`- \`${c}\``)
lines.push('')

const catName = data.category?.trim()
if (catName) {
const existing = categories.find((c) => c.name === catName)
if (existing) {
existing.articleCount = (existing.articleCount ?? 0) + 1
} else {
categories.push({
id: String(categories.length + 1),
name: catName,
slug: slugify(catName, { lower: true }),
description: '',
color: '',
articleCount: 1,
})
addedCategories.add(catName)
}
}
if (lines.length) {
const comment = lines.join('\n')
const { execSync } = await import('child_process')
execSync(`gh pr comment ${prNumber} --body "${comment.replace(/"/g, '\\"')}"`)

articles.push({ title, slug, category: data.category?.trim() ?? '' })
}

const filteredTags = tags.filter((t) => (t.articleCount ?? 0) > 0)
const filteredCategories = categories.filter((c) => (c.articleCount ?? 0) > 0)

await writeJSON(tagsPath, filteredTags)
await writeJSON(categoriesPath, filteredCategories)
await writeJSON(articlesPath, articles)

console.log(`Updated data for language: ${lang}`)
console.log(` Articles: ${articles.length}`)
console.log(` Categories: ${filteredCategories.length}`)
console.log(` Tags: ${filteredTags.length}`)

if (process.env.GITHUB_EVENT_PATH) {
const event = JSON.parse(await fs.readFile(process.env.GITHUB_EVENT_PATH, 'utf8'))
const prNumber = event.pull_request?.number
if (prNumber) {
const lines = []
if (addedTags.size) {
lines.push(`## New tags added for ${lang} (please add descriptions)`)
for (const t of addedTags) lines.push(`- \`${t}\``)
lines.push('')
}
if (addedCategories.size) {
lines.push(`## New categories added for ${lang} (please add descriptions)`)
for (const c of addedCategories) lines.push(`- \`${c}\``)
lines.push('')
}
if (lines.length) {
const comment = lines.join('\n')
const { execFileSync } = await import('child_process')
execFileSync('gh', ['pr', 'comment', String(prNumber), '--body', comment])
}
}
}
}
Expand Down
14 changes: 1 addition & 13 deletions src/components/Sidebar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import categoriesEn from '../data/en/categories.json'
import tagsEn from '../data/en/tags.json'
import LanguageSelector from './LanguageSelector.astro'
import { t } from '../i18n/index.ts'
import type { Category, Tag } from '../content.config'

interface Props {
lang: string
Expand All @@ -15,19 +16,6 @@ interface Props {

const { lang, currentPath, labels } = Astro.props

interface Category {
slug: string
name: string
description: string
articleCount: number
}

interface Tag {
slug: string
name: string
articleCount: number
}

const categories: Category[] = lang === 'en' ? categoriesEn : categoriesTr
const tags: Tag[] = lang === 'en' ? tagsEn : tagsTr

Expand Down
13 changes: 13 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders'

export interface Tag {
slug: string
name: string
articleCount: number
}

export interface Category {
slug: string
name: string
description: string
articleCount: number
}

const articles = defineCollection({
loader: glob({ pattern: '**/*{.md,.mdx}', base: './src/content/articles' }),
schema: z.object({
Expand Down
8 changes: 8 additions & 0 deletions src/data/en/og-labels.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"explore": "Explore Articles",
"category": "CATEGORY",
"article": "ARTICLE",
"tagline": "Neural networks, transformers, reinforcement learning & more",
"description": "Clear explanations of machine learning concepts",
"suffix": "ML Concepts Explained"
}
8 changes: 8 additions & 0 deletions src/data/tr/og-labels.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"explore": "Makaleleri Keşfet",
"category": "KATEGORİ",
"article": "MAKALE",
"tagline": "Sinir ağları, transformerlar, pekiştirmeli öğrenme ve daha fazlası",
"description": "Makine öğrenimi kavramlarının net açıklamaları",
"suffix": "ML Kavramları Açıklandı"
}
8 changes: 1 addition & 7 deletions src/pages/categories/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { getCollection } from 'astro:content'
import categoriesTr from '../../data/tr/categories.json'
import categoriesEn from '../../data/en/categories.json'
import { getLangFromUrl, t } from '../../i18n/index.ts'

interface Category {
slug: string
name: string
description: string
articleCount: number
}
import type { Category } from '../../content.config'

const lang = getLangFromUrl(Astro.url)
const labels = await import(`../../data/${lang}/labels.json`).then((m) => m.default)
Expand Down
Loading
Loading