feat: add RTL language support (#531)

* feat: add RTL language support

- Add `SITE.dir` config option.
- Replace physical Tailwind classes with logical
property classes.

Resolves #439

* docs: add `dir` section in doc
This commit is contained in:
Sat Naing
2025-06-14 00:07:27 +07:00
committed by GitHub
parent 76e15b55e3
commit 38ebf6e1b6
10 changed files with 29 additions and 22 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ import { SITE } from "@/config";
href="/" href="/"
class="focus-outline mt-8 mb-2 flex hover:text-foreground/75" class="focus-outline mt-8 mb-2 flex hover:text-foreground/75"
> >
<IconChevronLeft class="inline-block size-6" /> <IconChevronLeft class="inline-block size-6 rtl:rotate-180" />
<span>Go back</span> <span>Go back</span>
</LinkButton> </LinkButton>
</div> </div>
+2 -2
View File
@@ -6,8 +6,8 @@ import IconArrowNarrowUp from "@/assets/icons/IconArrowNarrowUp.svg";
<div <div
id="btt-btn-container" id="btt-btn-container"
class:list={[ class:list={[
"fixed right-4 bottom-8 z-50", "fixed end-4 bottom-8 z-50",
"md:sticky md:right-auto md:float-end md:mr-1", "md:sticky md:end-auto md:float-end md:me-1",
"translate-y-14 opacity-0 transition duration-500", "translate-y-14 opacity-0 transition duration-500",
]} ]}
> >
+3 -3
View File
@@ -27,7 +27,7 @@ const isActive = (path: string) => {
<a <a
id="skip-to-content" id="skip-to-content"
href="#main-content" href="#main-content"
class="absolute -top-full left-16 z-50 bg-background px-3 py-2 text-accent backdrop-blur-lg transition-all focus:top-4" class="absolute start-16 -top-full z-50 bg-background px-3 py-2 text-accent backdrop-blur-lg transition-all focus:top-4"
> >
Skip to content Skip to content
</a> </a>
@@ -47,7 +47,7 @@ const isActive = (path: string) => {
</a> </a>
<nav <nav
id="nav-menu" id="nav-menu"
class="flex w-full flex-col items-center sm:ml-2 sm:flex-row sm:justify-end sm:space-x-4 sm:py-0" class="flex w-full flex-col items-center sm:ms-2 sm:flex-row sm:justify-end sm:space-x-4 sm:py-0"
> >
<button <button
id="menu-btn" id="menu-btn"
@@ -65,7 +65,7 @@ const isActive = (path: string) => {
"mt-4 grid w-44 grid-cols-2 place-content-center gap-2", "mt-4 grid w-44 grid-cols-2 place-content-center gap-2",
"[&>li>a]:block [&>li>a]:px-4 [&>li>a]:py-3 [&>li>a]:text-center [&>li>a]:font-medium [&>li>a]:hover:text-accent sm:[&>li>a]:px-2 sm:[&>li>a]:py-1", "[&>li>a]:block [&>li>a]:px-4 [&>li>a]:py-3 [&>li>a]:text-center [&>li>a]:font-medium [&>li>a]:hover:text-accent sm:[&>li>a]:px-2 sm:[&>li>a]:py-1",
"hidden", "hidden",
"sm:mt-0 sm:ml-0 sm:flex sm:w-auto sm:gap-x-5 sm:gap-y-0", "sm:mt-0 sm:flex sm:w-auto sm:gap-x-5 sm:gap-y-0",
]} ]}
> >
<li class="col-span-2"> <li class="col-span-2">
+4 -4
View File
@@ -18,21 +18,21 @@ const { page } = Astro.props;
<LinkButton <LinkButton
disabled={!page.url.prev} disabled={!page.url.prev}
href={page.url.prev as string} href={page.url.prev as string}
class:list={["mr-4 select-none", { "opacity-50": !page.url.prev }]} class:list={["me-4 select-none", { "opacity-50": !page.url.prev }]}
ariaLabel="Previous" ariaLabel="Previous"
> >
<IconArrowLeft class="inline-block" /> <IconArrowLeft class="inline-block rtl:rotate-180" />
Prev Prev
</LinkButton> </LinkButton>
{page.currentPage} / {page.lastPage} {page.currentPage} / {page.lastPage}
<LinkButton <LinkButton
disabled={!page.url.next} disabled={!page.url.next}
href={page.url.next as string} href={page.url.next as string}
class:list={["ml-4 select-none", { "opacity-50": !page.url.next }]} class:list={["ms-4 select-none", { "opacity-50": !page.url.next }]}
ariaLabel="Next" ariaLabel="Next"
> >
Next Next
<IconArrowRight class="inline-block" /> <IconArrowRight class="inline-block rtl:rotate-180" />
</LinkButton> </LinkButton>
</nav> </nav>
) )
+3 -3
View File
@@ -20,15 +20,15 @@ const { tag, tagName, size = "sm" } = Astro.props;
href={`/tags/${tag}/`} href={`/tags/${tag}/`}
transition:name={tag} transition:name={tag}
class:list={[ class:list={[
"relative pr-2 text-lg underline decoration-dashed group-hover:-top-0.5 group-hover:text-accent focus-visible:p-1", "relative pe-2 text-lg underline decoration-dashed group-hover:-top-0.5 group-hover:text-accent focus-visible:p-1",
{ "text-sm": size === "sm" }, { "text-sm": size === "sm" },
]} ]}
> >
<IconHash <IconHash
class:list={[ class:list={[
"inline-block opacity-80", "inline-block opacity-80",
{ "-mr-3.5 size-4": size === "sm" }, { "-me-3.5 size-4": size === "sm" },
{ "-mr-5 size-6": size === "lg" }, { "-me-5 size-6": size === "lg" },
]} ]}
/> />
&nbsp;<span>{tagName}</span> &nbsp;<span>{tagName}</span>
+1
View File
@@ -17,6 +17,7 @@ export const SITE = {
url: "https://github.com/satnaing/astro-paper/edit/main/", url: "https://github.com/satnaing/astro-paper/edit/main/",
}, },
dynamicOgImage: true, dynamicOgImage: true,
dir: "ltr", // "rtl" | "auto"
lang: "en", // html lang code. Set this empty and default will be "en" lang: "en", // html lang code. Set this empty and default will be "en"
timezone: "Asia/Bangkok", // Default global timezone (IANA format) https://en.wikipedia.org/wiki/List_of_tz_database_time_zones timezone: "Asia/Bangkok", // Default global timezone (IANA format) https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
} as const; } as const;
@@ -43,6 +43,7 @@ export const SITE = {
url: "https://github.com/satnaing/astro-paper/edit/main/", url: "https://github.com/satnaing/astro-paper/edit/main/",
}, },
dynamicOgImage: true, // enable automatic dynamic og-image generation dynamicOgImage: true, // enable automatic dynamic og-image generation
dir: "ltr", // "rtl" | "auto"
lang: "en", // html lang code. Set this empty and default will be "en" lang: "en", // html lang code. Set this empty and default will be "en"
timezone: "Asia/Bangkok", // Default global timezone (IANA format) https://en.wikipedia.org/wiki/List_of_tz_database_time_zones timezone: "Asia/Bangkok", // Default global timezone (IANA format) https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
} as const; } as const;
@@ -66,6 +67,7 @@ Here are SITE configuration options
| `showBackButton` | Determines whether to display the `Go back` button in each blog post. | | `showBackButton` | Determines whether to display the `Go back` button in each blog post. |
| `editPost` | This option allows users to suggest changes to a blog post by providing an edit link under blog post titles. This feature can be disabled by setting `SITE.editPost.enabled` to `false`. | | `editPost` | This option allows users to suggest changes to a blog post by providing an edit link under blog post titles. This feature can be disabled by setting `SITE.editPost.enabled` to `false`. |
| `dynamicOgImage` | This option controls whether to [generate dynamic og-image](https://astro-paper.pages.dev/posts/dynamic-og-image-generation-in-astropaper-blog-posts/) if no `ogImage` is specified in the blog post frontmatter. If you have many blog posts, you might want to disable this feature. See the [trade-off](https://astro-paper.pages.dev/posts/dynamic-og-image-generation-in-astropaper-blog-posts/#trade-off) for more details. | | `dynamicOgImage` | This option controls whether to [generate dynamic og-image](https://astro-paper.pages.dev/posts/dynamic-og-image-generation-in-astropaper-blog-posts/) if no `ogImage` is specified in the blog post frontmatter. If you have many blog posts, you might want to disable this feature. See the [trade-off](https://astro-paper.pages.dev/posts/dynamic-og-image-generation-in-astropaper-blog-posts/#trade-off) for more details. |
| `dir` | Specifies the text direction of the entire blog. Used as [HTML dir attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir) in `<html dir="ltr">`. Supported values: `ltr` \| `rtl` \| `auto` |
| `lang` | Used as HTML ISO Language code in `<html lang"en">`. Default is `en`. | | `lang` | Used as HTML ISO Language code in `<html lang"en">`. Default is `en`. |
| `timezone` | This option allows you to specify your timezone using the [IANA format](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). Setting this ensures consistent timestamps across your localhost and deployed site, eliminating time differences. | | `timezone` | This option allows you to specify your timezone using the [IANA format](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). Setting this ensures consistent timestamps across your localhost and deployed site, eliminating time differences. |
+5 -1
View File
@@ -48,7 +48,11 @@ const structuredData = {
--- ---
<!doctype html> <!doctype html>
<html lang=`${SITE.lang ?? "en"}` class={`${scrollSmooth && "scroll-smooth"}`}> <html
dir={SITE.dir}
lang=`${SITE.lang ?? "en"}`
class={`${scrollSmooth && "scroll-smooth"}`}
>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
+5 -5
View File
@@ -132,7 +132,7 @@ const nextPost =
href={getPath(prevPost.id, prevPost.filePath)} href={getPath(prevPost.id, prevPost.filePath)}
class="flex w-full gap-1 hover:opacity-75" class="flex w-full gap-1 hover:opacity-75"
> >
<IconChevronLeft class="inline-block flex-none" /> <IconChevronLeft class="inline-block flex-none rtl:rotate-180" />
<div> <div>
<span>Previous Post</span> <span>Previous Post</span>
<div class="text-sm text-accent/85">{prevPost.title}</div> <div class="text-sm text-accent/85">{prevPost.title}</div>
@@ -144,13 +144,13 @@ const nextPost =
nextPost && ( nextPost && (
<a <a
href={getPath(nextPost.id, nextPost.filePath)} href={getPath(nextPost.id, nextPost.filePath)}
class="flex w-full justify-end gap-1 text-right hover:opacity-75 sm:col-start-2" class="flex w-full justify-end gap-1 text-end hover:opacity-75 sm:col-start-2"
> >
<div> <div>
<span>Next Post</span> <span>Next Post</span>
<div class="text-sm text-accent/85">{nextPost.title}</div> <div class="text-sm text-accent/85">{nextPost.title}</div>
</div> </div>
<IconChevronRight class="inline-block flex-none" /> <IconChevronRight class="inline-block flex-none rtl:rotate-180" />
</a> </a>
) )
} }
@@ -211,7 +211,7 @@ const nextPost =
heading.classList.add("group"); heading.classList.add("group");
const link = document.createElement("a"); const link = document.createElement("a");
link.className = link.className =
"heading-link ml-2 opacity-0 group-hover:opacity-100 focus:opacity-100"; "heading-link ms-2 opacity-0 group-hover:opacity-100 focus:opacity-100";
link.href = "#" + heading.id; link.href = "#" + heading.id;
const span = document.createElement("span"); const span = document.createElement("span");
@@ -235,7 +235,7 @@ const nextPost =
const copyButton = document.createElement("button"); const copyButton = document.createElement("button");
copyButton.className = copyButton.className =
"copy-code absolute right-3 -top-3 rounded bg-muted px-2 py-1 text-xs leading-4 text-foreground font-medium"; "copy-code absolute end-3 -top-3 rounded bg-muted px-2 py-1 text-xs leading-4 text-foreground font-medium";
copyButton.innerHTML = copyButtonLabel; copyButton.innerHTML = copyButtonLabel;
codeBlock.setAttribute("tabindex", "0"); codeBlock.setAttribute("tabindex", "0");
codeBlock.appendChild(copyButton); codeBlock.appendChild(copyButton);
+3 -3
View File
@@ -37,7 +37,7 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
<IconRss <IconRss
width={20} width={20}
height={20} height={20}
class="scale-125 stroke-accent stroke-3" class="scale-125 stroke-accent stroke-3 rtl:-rotate-90"
/> />
<span class="sr-only">RSS Feed</span> <span class="sr-only">RSS Feed</span>
</a> </a>
@@ -61,7 +61,7 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
// only display if at least one social link is enabled // only display if at least one social link is enabled
SOCIALS.length > 0 && ( SOCIALS.length > 0 && (
<div class="mt-4 flex flex-col sm:flex-row sm:items-center"> <div class="mt-4 flex flex-col sm:flex-row sm:items-center">
<div class="mr-2 mb-1 whitespace-nowrap sm:mb-0">Social Links:</div> <div class="me-2 mb-1 whitespace-nowrap sm:mb-0">Social Links:</div>
<Socials /> <Socials />
</div> </div>
) )
@@ -103,7 +103,7 @@ const recentPosts = sortedPosts.filter(({ data }) => !data.featured);
<div class="my-8 text-center"> <div class="my-8 text-center">
<LinkButton href="/posts/"> <LinkButton href="/posts/">
All Posts All Posts
<IconArrowRight class="inline-block" /> <IconArrowRight class="inline-block rtl:-rotate-180" />
</LinkButton> </LinkButton>
</div> </div>
</main> </main>