Skip to content

Commit

Permalink
Merge pull request #13 from tex0l/11-write-hello-world-20
Browse files Browse the repository at this point in the history
move to luxon instead of DIY-i18n functions for dates, and execute th…
  • Loading branch information
tex0l authored Mar 4, 2024
2 parents 6d0305c + cd1b128 commit 58f0784
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 99 deletions.
2 changes: 1 addition & 1 deletion astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default defineConfig({
}
}),
icon(),
alpinejs(),
alpinejs({ entrypoint: '/src/utils/alpineSetup' }),
react(),
moveOgImages()
],
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"astro-icon": "^1.0.2",
"astro-pintora": "^0.0.3-4",
"buffer": "^6.0.3",
"luxon": "^3.4.4",
"mdast-util-mdx-jsx": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -57,6 +58,7 @@
"devDependencies": {
"@astrojs/ts-plugin": "^1.5.3",
"@types/alpinejs": "^3.13.6",
"@types/luxon": "^3.4.2",
"@types/markdown-it": "^13.0.7",
"@types/mdast": "^4.0.3",
"@types/react": "^18.2.58",
Expand Down
26 changes: 0 additions & 26 deletions src/components/i18n/LanguagePicker.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,3 @@ const t = useTranslations(currentLang)
</select>
</label>
</form>
<script>
import { languages } from '~/i18n/index.ts'
import { navigate } from 'astro:transitions/client'

const switchLang = (path: string, lang: string): string => {
if (!Object.hasOwn(languages, lang)) throw new Error('Provided lang prefix is unknown')

const splitPath = path.split('/').filter(x => x !== '')

if (splitPath.length === 0) throw new Error('Once split, path is empty')
if (!Object.hasOwn(languages, splitPath[0])) throw new Error('Once split, lang prefix is unknown')
splitPath[0] = lang

return `/${splitPath.join('/')}/`
}

document.addEventListener('alpine:init', () => {
Alpine.data('languageSelector', () => ({
change($event: Event) {
const l = ($event.target as HTMLSelectElement).value
window.localStorage.setItem('i18n', l)
navigate(switchLang(window.location.pathname, l))
}
}))
})
</script>
4 changes: 2 additions & 2 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const languages: Record<string, Locale> = {
h1: 'Blog',
subtitle: 'Dans ce blog, je parle de changement climatique, de politique, d\'économie, de religion, de cybersécurité et d\'autres sujets qui m\'intéressent.',
disclaimer: 'Je n\'exprime ici que mon opinion, si vous pensez que j\'ai fait une erreur ou que mon opinion est faussée, n\'hésitez pas à me contacter !',
published: 'Publié {0}'
published: 'Publié '
},
layout: {
'language-selector': 'Changer de langue',
Expand Down Expand Up @@ -106,7 +106,7 @@ export const languages: Record<string, Locale> = {
h1: 'Blog',
subtitle: 'In this blog, I talk about climate change, politics, economics, religion, cybersecurity, and other topics I find interesting.',
disclaimer: 'I only express my opinion here, if you think I made a mistake or that my opinion is flawed, feel free to contact me!',
published: 'Published {0}'
published: 'Published '
},
layout: {
'language-selector': 'Switch lang',
Expand Down
47 changes: 0 additions & 47 deletions src/i18n/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,50 +64,3 @@ export function useTranslations<T extends Translation> (lang: LocaleId, localLoc
return t(key, args ?? {}, locales)
}
}

export const useFormatDate = (lang: LocaleId): ((date: Date) => string) => lang === 'en' ? formatDateFromNowEn : formatDateFromNowFr

const formatDateFromNowEn = (date: Date): string => {
let ago = 'ago'
let daysAgo = Math.floor(((new Date()).getTime() - date.getTime()) / 1000 / 3600 / 24)
if (Number.isNaN(daysAgo)) throw new Error('Could not process date')
if (daysAgo === 0) return 'today'
if (daysAgo === 1) return 'yesterday'
if (daysAgo === -1) return 'tomorrow'
if (daysAgo < 0) {
ago = 'from now'
daysAgo = -daysAgo
}
if (daysAgo < 7) return `${daysAgo} days ${ago}`
if (daysAgo < 14) return `${Math.floor(daysAgo / 7)} week ${ago}`
if (daysAgo < 30) return `${Math.floor(daysAgo / 7)} weeks ${ago}`
if (daysAgo < 60) return `${Math.floor(daysAgo / 30)} month ${ago}`
if (daysAgo < 365) return `${Math.floor(daysAgo / 30)} months ${ago}`
if (daysAgo < 365 * 2) return `${Math.floor(daysAgo / 365)} year ${ago}`
return `${Math.floor(daysAgo / 365)} years ${ago}`
}

export const formatDateFromNowFr = (date: Date): string => {
let ago = 'il y a'
let daysAgo = Math.floor(((new Date()).getTime() - date.getTime()) / 1000 / 3600 / 24)
if (Number.isNaN(daysAgo)) throw new Error('Could not process date')
if (daysAgo === 0) return 'aujourd\'hui'
if (daysAgo === 1) return 'hier'
if (daysAgo === -1) return 'demain'
if (daysAgo < 0) {
ago = 'dans'
daysAgo = -daysAgo
}
if (daysAgo < 7) return `${ago} ${daysAgo} jours`
if (daysAgo < 14) return `${ago} ${Math.floor(daysAgo / 7)} semaine`
if (daysAgo < 30) return `${ago} ${Math.floor(daysAgo / 7)} semaines`
if (daysAgo < 60) return `${ago}${Math.floor(daysAgo / 30)} mois`
if (daysAgo < 365) return `${ago} ${Math.floor(daysAgo / 30)} mois`
if (daysAgo < 365 * 2) return `${ago} ${Math.floor(daysAgo / 365)} an`
return `${ago} ${Math.floor(daysAgo / 365)} ans`
}

export const formatDateAbsolute = (rawDate: number | string | Date): string => {
const date = new Date(rawDate)
return `${date.getUTCFullYear().toString().padStart(4, '0')}/${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}`
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
---
import { readFile } from 'node:fs/promises'
import { convertMDXToHTML } from '../../../../utils/mdxToHtml'
import { languages } from '../../../../i18n'
import { convertMDXToHTML } from '~/utils/mdxToHtml'
import { languages } from '~/i18n'
import {
formatDateAbsolute,
useFormatDate,
useTranslations,
validateLang
} from '../../../../i18n/utils'
} from '~/i18n/utils'
import { Picture } from 'astro:assets'
import Layout from '../../../../layouts/Layout.astro'
import Layout from '~/layouts/Layout.astro'
import { getCollection, type CollectionEntry } from 'astro:content'
import { slugify } from '../../../../utils'
import { slugify } from '~/utils'
import sanitizeHtml from 'sanitize-html'
import MetaOGImage from '../../../../components/MetaOGImage.astro'
import MetaOGImage from '~/components/MetaOGImage.astro'
import { DateTime } from 'luxon'
export async function getStaticPaths (): Promise<Array<{ params: { lang: string, slug: string }, props: { post: CollectionEntry<'blog'> } }>> {
const blogEntries = await getCollection('blog')
Expand All @@ -35,34 +34,40 @@ const { post } = Astro.props as Props
const { Content } = await post.render()
const { lang } = Astro.params
validateLang(lang)
const formatDate = useFormatDate(lang)
const t = useTranslations(lang)
const truncatedPost = sanitizeHtml(await convertMDXToHTML(post.body, 297), { allowedTags: [] })
const imageFilePath = (post.data.image != null && Object.hasOwn(post.data.image, 'fsPath')) ? (post.data.image as unknown as { fsPath: string }).fsPath : ''
const imageFilePath = (post.data.image != null && Object.hasOwn(post.data.image, 'fsPath')) ? (post.data.image as unknown as {
fsPath: string
}).fsPath : ''
const image = imageFilePath != null ? await readFile(imageFilePath) : undefined
const date = DateTime.fromJSDate(post.data.date).setLocale(languages[lang].code)
const formattedAbsoluteDate = date.toLocaleString(DateTime.DATE_FULL)
---

<Layout title={post.data.title} description={post.data.description ?? `${truncatedPost}...`}>
<Fragment slot="head">
<meta property="og:type" content="article"/>
{
(image != null)
? <MetaOGImage title={post.data.title}
description={post.data.description ?? truncatedPost}
author="Timothée Rebours"
date={post.data.date}
dateFormat={lang}
image={image}
?
<MetaOGImage title={post.data.title}
description={post.data.description ?? truncatedPost}
author="Timothée Rebours"
date={post.data.date}
dateFormat={lang}
image={image}
/>
: <></>
:
<></>
}
</Fragment>
<div class="space-y-2 mb-2">
<h1 class="text-5xl font-medium text-center">{post.data.title}</h1>
<p class="text-gray-500 text-center" title={formatDateAbsolute(post.data.date)}>
{t('blog.published', [formatDate(post.data.date)])}
<p class="text-gray-500 text-center" title={formattedAbsoluteDate} x-data={`formatDate("${post.data.date.toString()}", "${languages[lang].code}", false)`}>
{t('blog.published')}<span x-text="formattedDate">{`${lang === 'fr' ? 'le' : 'on'} ${formattedAbsoluteDate}`}</span>
</p>
</div>
<div class="max-h-60 md:max-h-72 lg:max-h-96 overflow-hidden flex items-center justify-center">
Expand Down
45 changes: 41 additions & 4 deletions src/pages/[lang]/blog/index.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { useFormatDate, formatDateAbsolute, useTranslatedPath, useTranslations, validateLang } from '~/i18n/utils'
import { useTranslatedPath, useTranslations, validateLang } from '~/i18n/utils'
import { Picture } from 'astro:assets'
import Layout from '~/layouts/Layout.astro'
import { getCollection } from 'astro:content'
Expand All @@ -8,6 +8,7 @@ import { languages } from '~/i18n/index.ts'
import { convertMDXToHTML } from '~/utils/mdxToHtml'
import portrait from '~/resources/portrait-blog.png?arraybuffer'
import MetaOGImage from '~/components/MetaOGImage.astro'
import { DateTime } from 'luxon'
export function getStaticPaths (): Array<{ params: { lang: string } }> {
return Object.keys(languages).map(lang => ({ params: { lang } }))
Expand All @@ -21,8 +22,44 @@ const localizePath = useTranslatedPath(lang)
const blogPosts = (await getCollection('blog', ({ id }) => id.split('/')[0] === lang))
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
const renderedBlogPosts = await Promise.all(blogPosts.map(async post => ({ ...post, html: `${await convertMDXToHTML(post.body, 100)}...` })))
const formatDate = useFormatDate(lang)
const formatDate = (jSDate: Date, lang: string): { formatted: string, absolute: string } => {
const date = DateTime.fromJSDate(jSDate).setLocale(lang)
const delta = Math.abs(date.diffNow().as('days'))
const formattedAbsoluteDate = date.toLocaleString(DateTime.DATE_FULL)
const formattedRelativeDate = date.toRelative()
if (delta <= 7) {
return {
formatted: formattedRelativeDate,
absolute: formattedAbsoluteDate
}
}
if (delta <= 30) {
return {
formatted: `${formattedRelativeDate} (${formattedAbsoluteDate})`,
absolute: formattedAbsoluteDate
}
}
return {
formatted: formattedAbsoluteDate,
absolute: formattedAbsoluteDate
}
}
const renderedBlogPosts = await Promise.all(blogPosts.map(async post => {
const { formatted, absolute } = formatDate(post.data.date, languages[lang].code)
return ({
...post,
formattedAbsoluteDate: absolute,
formattedDate: formatted,
html: `${await convertMDXToHTML(post.body, 100)}...`
})
}))
---

<Layout title={t('blog.title')}>
Expand Down Expand Up @@ -54,7 +91,7 @@ const formatDate = useFormatDate(lang)
<h2 class="text-xl font-medium mb-4">{post.data.title}</h2>
<div class="italic text-justify mb-4 line-clamp-3 px-1 text-ellipsis overflow-hidden"><Fragment set:html={post.data.description ?? post.html}/></div>
<div class="absolute bottom-6 right-6 text-sm text-gray-600">
<div class="first-letter:uppercase" title={formatDateAbsolute(post.data.date)}>{formatDate(post.data.date)}</div>
<div class="first-letter:uppercase" x-data={`formatDate("${post.data.date.toString()}", "${languages[lang].code}", true)`} title={post.formattedAbsoluteDate} x-text="formattedDate">{`${post.formattedAbsoluteDate}`}</div>
</div>
</div>
</a>
Expand Down
43 changes: 43 additions & 0 deletions src/utils/alpineSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Alpine } from 'alpinejs'
import { DateTime } from 'luxon'
import { navigate } from 'astro:transitions/client'
import { languages } from '~/i18n'

const formatDate = (jSDate: Date, lang: string, shortVersion: boolean): string => {
const date = DateTime.fromJSDate(jSDate).setLocale(lang)

const delta = Math.abs(date.diffNow().as('days'))

const formattedAbsoluteDate = date.toLocaleString(DateTime.DATE_FULL)
const formattedRelativeDate = date.toRelative()

if (shortVersion && delta <= 7 && formattedRelativeDate != null) return formattedRelativeDate
if (delta <= 30 && formattedRelativeDate != null) return `${formattedRelativeDate} (${formattedAbsoluteDate})`
return `${!shortVersion ? lang === 'fr-FR' ? 'le ' : 'on ' : ''}${formattedAbsoluteDate}`
}

const switchLang = (path: string, lang: string): string => {
if (!Object.hasOwn(languages, lang)) throw new Error('Provided lang prefix is unknown')

const splitPath = path.split('/').filter(x => x !== '')

if (splitPath.length === 0) throw new Error('Once split, path is empty')
if (!Object.hasOwn(languages, splitPath[0])) throw new Error('Once split, lang prefix is unknown')
splitPath[0] = lang

return `/${splitPath.join('/')}/`
}

export default (Alpine: Alpine): void => {
Alpine.data('formatDate', (jsDateString: Date, lang: string, shortVersion: boolean) => {
return {
formattedDate: formatDate(new Date(jsDateString), lang, shortVersion)
}
})

Alpine.data('languageSelector', () => ({
change ($event: Event) {
navigate(switchLang(window.location.pathname, ($event.target as HTMLSelectElement).value)).catch(console.error)
}
}))
}

0 comments on commit 58f0784

Please sign in to comment.