Implement core i18n infrastructure with English support
Co-authored-by: setoelkahfi <1797197+setoelkahfi@users.noreply.github.com>
copilot-swe-agent[bot] committed
Oct 10, 2025 at 06:54 UTC
90bed593b58a3eac86a2c79d88218f3e366434f1
16 files changed
+540
-27
astro.config.ts
+7
@@ -17,6 +17,13 @@ import tailwindcss from '@tailwindcss/vite';
17
18
export default defineConfig({
19
site: SITE.website,
20
+ i18n: {
21
+ defaultLocale: 'id',
22
+ locales: ['id', 'en'],
23
+ routing: {
24
+ prefixDefaultLocale: false
25
+ }
26
+ },
27
devToolbar: {
28
enabled: false
29
},
src/components/layout/Footer.astro
+9
-5
@@ -1,5 +1,9 @@
1
---
2
import { SITE } from '@/config'
3
+import { getLangFromUrl, useTranslations } from '@/i18n/utils'
4
+
5
+const currentLang = getLangFromUrl(Astro.url)
6
+const t = useTranslations(currentLang)
7
const today = new Date()
8
---
9
@@ -7,9 +11,9 @@ const today = new Date()
11
<div class="footer-content">
12
<div class="footer-links">
13
<nav class="footer-nav">
10
- <a href="/contact">Contact</a>
11
- <a href="/privacy-policy">Privacy Policy</a>
12
- <a href="/terms-of-use">Terms of Use</a>
14
+ <a href={`${currentLang === 'id' ? '' : '/' + currentLang}/contact`}>{t('nav.contact')}</a>
15
+ <a href={`${currentLang === 'id' ? '' : '/' + currentLang}/privacy-policy`}>{t('nav.privacy')}</a>
16
+ <a href={`${currentLang === 'id' ? '' : '/' + currentLang}/terms-of-use`}>{t('nav.terms')}</a>
17
</nav>
18
</div>
19
<div class="footer-bottom">
@@ -21,9 +25,9 @@ const today = new Date()
25
<a href="https://5mb.app" target="__blank">{SITE.author}</a>
26
</div>
27
<div class="powered-by">
24
- Powered by{' '}
28
+ {t('footer.poweredBy')}{' '}
29
<a href="https://smbcloud.xyz" target="__blank">smbCloud</a> |{' '}
26
- <a href="https://github.com/smbcloudXYZ/pepakbasajawa-web" target="__blank">Source</a>
30
+ <a href="https://github.com/smbcloudXYZ/pepakbasajawa-web" target="__blank">{t('footer.source')}</a>
31
</div>
32
</div>
33
</div>
src/components/layout/Header.astro
+2
@@ -1,6 +1,7 @@
1
---
2
import { SITE } from '@/config'
3
import ThemeToggle from '@/components/ui/ThemeToggle.astro'
4
+import LanguageSwitcher from '@/components/ui/LanguageSwitcher.astro'
5
import { Image } from 'astro:assets'
6
---
7
@@ -29,6 +30,7 @@ import { Image } from 'astro:assets'
30
/>
31
</a>
32
</div>
33
+ <LanguageSwitcher />
34
<ThemeToggle />
35
</div>
36
</nav>
src/components/pages/About.astro
+4
-1
@@ -1,8 +1,11 @@
1
---
2
import { getEntry } from 'astro:content'
3
import { render } from 'astro:content'
4
+import { getLangFromUrl } from '@/i18n/utils'
5
5
-const aboutEntry = await getEntry('about', 'about')
6
+const currentLang = getLangFromUrl(Astro.url)
7
+const aboutId = currentLang === 'en' ? 'en/about' : 'about'
8
+const aboutEntry = await getEntry('about', aboutId)
9
// Check if there is actual content (excluding comments)
10
const hasContent = aboutEntry?.body
11
? aboutEntry.body.replace(/<!--[\s\S]*?-->/g, '').trim().length > 0
src/components/ui/LanguageSwitcher.astro
new
+66
@@ -0,0 +1,66 @@
1
+---
2
+import { getLangFromUrl, getAlternateLanguage, getAlternateUrl, useTranslations } from '@/i18n/utils'
3
+import { languages } from '@/i18n/ui'
4
+
5
+const currentLang = getLangFromUrl(Astro.url)
6
+const alternateLang = getAlternateLanguage(currentLang)
7
+const alternateUrl = getAlternateUrl(Astro.url, alternateLang)
8
+const t = useTranslations(currentLang)
9
+---
10
+
11
+<div class="language-switcher">
12
+ <a href={alternateUrl} class="lang-link" title={t('language.switch')}>
13
+ <svg
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ width="20"
16
+ height="20"
17
+ viewBox="0 0 24 24"
18
+ fill="none"
19
+ stroke="currentColor"
20
+ stroke-width="2"
21
+ stroke-linecap="round"
22
+ stroke-linejoin="round"
23
+ >
24
+ <circle cx="12" cy="12" r="10"></circle>
25
+ <line x1="2" y1="12" x2="22" y2="12"></line>
26
+ <path
27
+ d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
28
+ ></path>
29
+ </svg>
30
+ <span class="lang-text">{alternateLang.toUpperCase()}</span>
31
+ </a>
32
+</div>
33
+
34
+<style>
35
+ .language-switcher {
36
+ display: flex;
37
+ align-items: center;
38
+ }
39
+
40
+ .lang-link {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 0.375rem;
44
+ padding: 0.5rem 0.75rem;
45
+ border-radius: 0.375rem;
46
+ color: var(--text-secondary);
47
+ text-decoration: none;
48
+ transition: all 0.2s ease-out;
49
+ font-size: var(--font-size-s);
50
+ font-weight: var(--font-weight-medium);
51
+ }
52
+
53
+ .lang-link:hover {
54
+ color: var(--text-primary);
55
+ background-color: var(--code-bg);
56
+ }
57
+
58
+ .lang-text {
59
+ font-weight: 600;
60
+ }
61
+
62
+ svg {
63
+ width: 1.125rem;
64
+ height: 1.125rem;
65
+ }
66
+</style>
src/content/about/en/about.md
new
+24
@@ -0,0 +1,24 @@
1
+---
2
+title: About
3
+---
4
+
5
+# Welcome to PBJ Komplit!
6
+
7
+**PBJ Komplit!** is a comprehensive and free resource for learning Javanese language. We are committed to preserving and introducing the richness of the Javanese language to everyone.
8
+
9
+## About Us
10
+
11
+PBJ Komplit! was founded with the goal of providing easy and fun access to Javanese language learning. We believe that language is a window to culture, and Javanese has an extraordinary cultural richness to explore.
12
+
13
+## What We Offer
14
+
15
+- **Language Lessons**: From beginner to advanced levels
16
+- **Vocabulary**: Collection of words and expressions in Javanese
17
+- **Grammar**: Explanations about Javanese language structure and rules
18
+- **Culture**: Insights into Javanese traditions and values through its language
19
+
20
+## Our Philosophy
21
+
22
+We believe that the best learning happens in a comfortable and friendly environment. This site is designed with principles of simplicity, clarity, and focus on useful content.
23
+
24
+_Thank you for visiting PBJ Komplit!. Happy learning!_
src/content/posts/en/privacy-policy.md
new
+66
@@ -0,0 +1,66 @@
1
+---
2
+title: Privacy Policy
3
+pubDate: '2025-07-12'
4
+---
5
+
6
+# Privacy Policy for PBJ Komplit!
7
+
8
+*Last updated: July 12, 2023*
9
+
10
+## Introduction
11
+
12
+Welcome to PBJ Komplit! We value your trust and are committed to protecting your privacy. This Privacy Policy explains how we collect, use, and protect the information you provide when using our website.
13
+
14
+## No Data Collection
15
+
16
+PBJ Komplit! does not collect, store, or track any user data. We are committed to user privacy, and our site is designed to function without the need to collect personal information.
17
+
18
+We do not use:
19
+- Tracking cookies
20
+- Analytics
21
+- User data collection
22
+- Login systems requiring personal information
23
+- Other automated data collection technologies
24
+
25
+## Our Commitment to Privacy
26
+
27
+We are committed to providing Javanese language learning services without collecting personal user data. This means:
28
+- You can learn Javanese without worrying about tracking or data collection
29
+- We do not analyze user behavior or create usage profiles
30
+- There is no data that we need to protect because we do not collect it
31
+
32
+## No Cookies or Tracking Technologies
33
+
34
+PBJ Komplit! does not use cookies, pixels, or other tracking technologies. Our site is designed to provide a Javanese language learning experience without storing information on your device or tracking your activity.
35
+
36
+## No Information Sharing
37
+
38
+Because we do not collect personal information, we have no user data to share with any third parties.
39
+
40
+## Security
41
+
42
+While we do not collect user data, we still pay attention to the security of our site to ensure a safe experience for all visitors.
43
+
44
+## Your Privacy Rights
45
+
46
+Because we do not collect any personal data, you do not need to worry about managing or deleting your data from our system. Your privacy is fully guaranteed as there is no data to access, correct, or delete.
47
+
48
+## Safe Site for All Ages
49
+
50
+Our site is safe for visitors of all ages, including children, because we do not collect personal information from anyone. Our content focuses on Javanese language learning and is available to everyone without needing to provide any data.
51
+
52
+## Changes to This Privacy Policy
53
+
54
+We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date at the top.
55
+
56
+## Contact Us
57
+
58
+If you have any questions about this Privacy Policy or our practices, please contact us at:
59
+
60
+Email: [info@pepakbasajawa.com](mailto:info@pepakbasajawa.com)
61
+
62
+Website: [https://pepakbasajawa.5mb.app/](https://pepakbasajawa.5mb.app/)
63
+
64
+---
65
+
66
+*PBJ Komplit! - Learn Javanese without worrying about your privacy.*
src/content/posts/en/terms-of-use.md
new
+81
@@ -0,0 +1,81 @@
1
+---
2
+title: Terms of Use
3
+pubDate: '2025-07-12'
4
+---
5
+
6
+# Terms of Use for PBJ Komplit!
7
+
8
+*Last updated: July 12, 2023*
9
+
10
+## Agreement to Terms
11
+
12
+By accessing and using PBJ Komplit!, you agree to be bound by these Terms of Use and all applicable laws and regulations. If you do not agree with any of these terms, you are prohibited from using or accessing this site.
13
+
14
+## Use License
15
+
16
+Permission is granted to temporarily access the materials (information or software) on PBJ Komplit!'s website for personal, non-commercial viewing only. This is the grant of a license, not a transfer of title, and under this license you may not:
17
+
18
+- Modify or copy the materials
19
+- Use the materials for any commercial purpose or for any public display
20
+- Attempt to reverse engineer any software contained on PBJ Komplit!'s website
21
+- Remove any copyright or other proprietary notations from the materials
22
+- Transfer the materials to another person or "mirror" the materials on any other server
23
+
24
+This license shall automatically terminate if you violate any of these restrictions and may be terminated by PBJ Komplit! at any time.
25
+
26
+## Educational Purpose
27
+
28
+PBJ Komplit! is designed for educational purposes to help people learn the Javanese language. All content is provided for informational and educational use only.
29
+
30
+## User Conduct
31
+
32
+When using our website, you agree to:
33
+- Use the site only for lawful purposes
34
+- Not interfere with or disrupt the site or servers
35
+- Not attempt to gain unauthorized access to any portion of the site
36
+- Respect the intellectual property rights of content creators
37
+- Not post or transmit harmful content
38
+
39
+## Content Accuracy
40
+
41
+While we strive to provide accurate and up-to-date information about the Javanese language, we make no warranties about the completeness, reliability, or accuracy of this information. Any reliance you place on such information is strictly at your own risk.
42
+
43
+## Intellectual Property
44
+
45
+The content on PBJ Komplit!, including but not limited to text, graphics, images, and code, is the property of PBJ Komplit! or its content suppliers and is protected by copyright and other intellectual property laws.
46
+
47
+## External Links
48
+
49
+Our website may contain links to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites and cannot accept responsibility or liability for their respective privacy policies or terms of use.
50
+
51
+## Disclaimer
52
+
53
+The materials on PBJ Komplit!'s website are provided "as is." PBJ Komplit! makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties. Furthermore, PBJ Komplit! does not warrant or make any representations concerning the accuracy, likely results, or reliability of the use of the materials on its website.
54
+
55
+## Limitations
56
+
57
+In no event shall PBJ Komplit! or its suppliers be liable for any damages (including, without limitation, damages for loss of data or profit, or due to business interruption) arising out of the use or inability to use the materials on PBJ Komplit!'s website.
58
+
59
+## Revisions and Errors
60
+
61
+The materials appearing on PBJ Komplit!'s website may include technical, typographical, or photographic errors. PBJ Komplit! does not warrant that any of the materials on its website are accurate, complete, or current. PBJ Komplit! may make changes to the materials contained on its website at any time without notice.
62
+
63
+## Changes to Terms
64
+
65
+PBJ Komplit! may revise these Terms of Use at any time without notice. By using this website, you are agreeing to be bound by the current version of these Terms of Use.
66
+
67
+## Governing Law
68
+
69
+These terms and conditions are governed by and construed in accordance with applicable laws, and you irrevocably submit to the exclusive jurisdiction of the courts in that location.
70
+
71
+## Contact Information
72
+
73
+If you have any questions about these Terms of Use, please contact us at:
74
+
75
+Email: [info@pepakbasajawa.com](mailto:info@pepakbasajawa.com)
76
+
77
+Website: [https://pepakbasajawa.5mb.app/](https://pepakbasajawa.5mb.app/)
78
+
79
+---
80
+
81
+*Thank you for using PBJ Komplit! to learn the Javanese language.*
src/i18n/ui.ts
new
+75
@@ -0,0 +1,75 @@
1
+export const languages = {
2
+ en: 'English',
3
+ id: 'Bahasa Indonesia'
4
+}
5
+
6
+export const defaultLang = 'id'
7
+
8
+export const ui = {
9
+ en: {
10
+ 'nav.home': 'Home',
11
+ 'nav.contact': 'Contact',
12
+ 'nav.privacy': 'Privacy Policy',
13
+ 'nav.terms': 'Terms of Use',
14
+ 'footer.copyright': '© {year}',
15
+ 'footer.poweredBy': 'Powered by',
16
+ 'footer.source': 'Source',
17
+ 'contact.title': 'Contact Us',
18
+ 'contact.subtitle': 'Have questions about learning Javanese language? Want to contribute to our content? We\'d love to hear from you!',
19
+ 'contact.getInTouch': 'Get in Touch',
20
+ 'contact.reachOut': 'Feel free to reach out to us via email:',
21
+ 'contact.response': 'We\'ll do our best to respond to your inquiry as soon as possible.',
22
+ 'contact.whatToAsk': 'What can you contact us about?',
23
+ 'contact.question1': 'Questions about Javanese language learning materials',
24
+ 'contact.question2': 'Suggestions for new content or improvements',
25
+ 'contact.question3': 'Technical issues with the website',
26
+ 'contact.question4': 'Collaboration opportunities',
27
+ 'contact.question5': 'General feedback about PBJ Komplit!',
28
+ 'about.welcome': 'Welcome to PBJ Komplit!',
29
+ 'about.description': '**PBJ Komplit!** is a comprehensive and free resource for learning Javanese language. We are committed to preserving and introducing the richness of the Javanese language to everyone.',
30
+ 'about.aboutUs': 'About Us',
31
+ 'about.aboutUsText': 'PBJ Komplit! was founded with the goal of providing easy and fun access to Javanese language learning. We believe that language is a window to culture, and Javanese has an extraordinary cultural richness to explore.',
32
+ 'about.whatWeOffer': 'What We Offer',
33
+ 'about.offer1': '**Language Lessons**: From beginner to advanced levels',
34
+ 'about.offer2': '**Vocabulary**: Collection of words and expressions in Javanese',
35
+ 'about.offer3': '**Grammar**: Explanations about Javanese language structure and rules',
36
+ 'about.offer4': '**Culture**: Insights into Javanese traditions and values through its language',
37
+ 'about.philosophy': 'Our Philosophy',
38
+ 'about.philosophyText': 'We believe that the best learning happens in a comfortable and friendly environment. This site is designed with principles of simplicity, clarity, and focus on useful content.',
39
+ 'about.thanks': 'Thank you for visiting PBJ Komplit!. Happy learning!',
40
+ 'language.switch': 'Switch Language'
41
+ },
42
+ id: {
43
+ 'nav.home': 'Beranda',
44
+ 'nav.contact': 'Kontak',
45
+ 'nav.privacy': 'Kebijakan Privasi',
46
+ 'nav.terms': 'Syarat Penggunaan',
47
+ 'footer.copyright': '© {year}',
48
+ 'footer.poweredBy': 'Didukung oleh',
49
+ 'footer.source': 'Sumber',
50
+ 'contact.title': 'Hubungi Kami',
51
+ 'contact.subtitle': 'Punya pertanyaan tentang pembelajaran bahasa Jawa? Ingin berkontribusi pada konten kami? Kami senang mendengar dari Anda!',
52
+ 'contact.getInTouch': 'Hubungi Kami',
53
+ 'contact.reachOut': 'Jangan ragu untuk menghubungi kami melalui email:',
54
+ 'contact.response': 'Kami akan berusaha sebaik mungkin untuk merespons pertanyaan Anda sesegera mungkin.',
55
+ 'contact.whatToAsk': 'Apa yang bisa Anda hubungi kami?',
56
+ 'contact.question1': 'Pertanyaan tentang materi pembelajaran bahasa Jawa',
57
+ 'contact.question2': 'Saran untuk konten baru atau perbaikan',
58
+ 'contact.question3': 'Masalah teknis dengan situs web',
59
+ 'contact.question4': 'Peluang kolaborasi',
60
+ 'contact.question5': 'Umpan balik umum tentang PBJ Komplit!',
61
+ 'about.welcome': 'Selamat Datang di PBJ Komplit!',
62
+ 'about.description': '**PBJ Komplit!** adalah sumber belajar bahasa Jawa yang lengkap dan gratis. Kami berkomitmen untuk melestarikan dan memperkenalkan kekayaan bahasa Jawa kepada semua kalangan.',
63
+ 'about.aboutUs': 'Tentang Kami',
64
+ 'about.aboutUsText': 'PBJ Komplit! didirikan dengan tujuan menyediakan akses pembelajaran bahasa Jawa yang mudah dan menyenangkan. Kami percaya bahwa bahasa adalah jendela menuju budaya, dan bahasa Jawa memiliki kekayaan budaya yang luar biasa untuk dieksplorasi.',
65
+ 'about.whatWeOffer': 'Yang Kami Tawarkan',
66
+ 'about.offer1': '**Pelajaran Bahasa**: Dari tingkat dasar hingga mahir',
67
+ 'about.offer2': '**Kosakata**: Kumpulan kata dan ungkapan dalam bahasa Jawa',
68
+ 'about.offer3': '**Tata Bahasa**: Penjelasan tentang struktur dan aturan bahasa Jawa',
69
+ 'about.offer4': '**Budaya**: Wawasan tentang tradisi dan nilai-nilai Jawa melalui bahasanya',
70
+ 'about.philosophy': 'Filosofi Kami',
71
+ 'about.philosophyText': 'Kami percaya bahwa pembelajaran terbaik terjadi dalam lingkungan yang nyaman dan ramah. Situs ini dirancang dengan prinsip kesederhanaan, kejelasan, dan fokus pada konten yang bermanfaat.',
72
+ 'about.thanks': 'Matur nuwun sampun sowan ing PBJ Komplit!. Sugeng sinau!',
73
+ 'language.switch': 'Ganti Bahasa'
74
+ }
75
+} as const
src/i18n/utils.ts
new
+46
@@ -0,0 +1,46 @@
1
+import { ui, defaultLang } from './ui'
2
+
3
+export function getLangFromUrl(url: URL) {
4
+ const [, lang] = url.pathname.split('/')
5
+ if (lang in ui) return lang as keyof typeof ui
6
+ return defaultLang
7
+}
8
+
9
+export function useTranslations(lang: keyof typeof ui) {
10
+ return function t(key: keyof (typeof ui)[typeof defaultLang]) {
11
+ return ui[lang][key] || ui[defaultLang][key]
12
+ }
13
+}
14
+
15
+export function getLocalizedPath(path: string, lang: string) {
16
+ if (lang === defaultLang) {
17
+ return path
18
+ }
19
+ return `/${lang}${path}`
20
+}
21
+
22
+export function getAlternateLanguage(currentLang: string) {
23
+ return currentLang === 'id' ? 'en' : 'id'
24
+}
25
+
26
+export function getAlternateUrl(url: URL, targetLang: string) {
27
+ const currentLang = getLangFromUrl(url)
28
+ let pathname = url.pathname
29
+
30
+ // Remove current language prefix if it exists
31
+ if (currentLang !== defaultLang) {
32
+ pathname = pathname.replace(`/${currentLang}`, '')
33
+ }
34
+
35
+ // Add target language prefix if it's not the default language
36
+ if (targetLang !== defaultLang) {
37
+ pathname = `/${targetLang}${pathname}`
38
+ }
39
+
40
+ // Ensure pathname starts with /
41
+ if (!pathname.startsWith('/')) {
42
+ pathname = `/${pathname}`
43
+ }
44
+
45
+ return pathname
46
+}
src/layouts/BaseLayout.astro
+3
-1
@@ -2,6 +2,7 @@
2
import { SITE } from '@/config'
3
import TransitionWrapper from '@/components/layout/TransitionWrapper.astro'
4
import type { LayoutProps } from '@/types'
5
+import { getLangFromUrl } from '@/i18n/utils'
6
7
type Props = LayoutProps
8
@@ -11,7 +12,8 @@ const widthValue = Math.min(parseFloat(contentWidth), 50)
12
const shouldUseCustomWidth = widthValue > 25
13
const finalWidth = shouldUseCustomWidth ? `${widthValue}rem` : '25rem'
14
14
-const language = SITE.language || 'en-US'
15
+const currentLang = getLangFromUrl(Astro.url)
16
+const language = currentLang === 'id' ? 'id-ID' : 'en-US'
17
---
18
19
<html
src/pages/[...slug].astro
+24
-6
@@ -2,15 +2,33 @@
2
import { type CollectionEntry, getCollection } from 'astro:content'
3
import PostLayout from '@/layouts/PostLayout.astro'
4
import { render } from 'astro:content'
5
+import { getLangFromUrl } from '@/i18n/utils'
6
7
export async function getStaticPaths() {
8
const posts = await getCollection('posts')
8
- return posts
9
- .filter((post) => !post.id.startsWith('_'))
10
- .map((post) => ({
11
- params: { slug: post.id },
12
- props: post
13
- }))
9
+ const paths = []
10
+
11
+ // Filter out draft posts (starting with _)
12
+ const publishedPosts = posts.filter((post) => !post.id.startsWith('_'))
13
+
14
+ for (const post of publishedPosts) {
15
+ // Check if it's an English post
16
+ if (post.id.startsWith('en/')) {
17
+ const slug = post.id.replace('en/', '')
18
+ paths.push({
19
+ params: { slug: `en/${slug}` },
20
+ props: post
21
+ })
22
+ } else {
23
+ // Indonesian post (default)
24
+ paths.push({
25
+ params: { slug: post.id },
26
+ props: post
27
+ })
28
+ }
29
+ }
30
+
31
+ return paths
32
}
33
type Props = CollectionEntry<'posts'>
34
src/pages/contact.astro
+16
-13
@@ -1,36 +1,39 @@
1
---
2
import IndexLayout from '@/layouts/IndexLayout.astro'
3
import { SITE } from '@/config'
4
+import { getLangFromUrl, useTranslations } from '@/i18n/utils'
5
+
6
+const currentLang = getLangFromUrl(Astro.url)
7
+const t = useTranslations(currentLang)
8
---
9
6
-<IndexLayout title={`Contact - ${SITE.title}`} description="Get in touch with PBJ Komplit!">
10
+<IndexLayout title={`${t('contact.title')} - ${SITE.title}`} description={t('contact.subtitle')}>
11
<div class="contact-page prose">
8
- <h1>Contact Us</h1>
12
+ <h1>{t('contact.title')}</h1>
13
14
<p>
11
- Have questions about learning Javanese language? Want to contribute to our content? We'd love
12
- to hear from you!
15
+ {t('contact.subtitle')}
16
</p>
17
18
<div class="contact-info">
16
- <h2>Get in Touch</h2>
17
- <p>Feel free to reach out to us via email:</p>
19
+ <h2>{t('contact.getInTouch')}</h2>
20
+ <p>{t('contact.reachOut')}</p>
21
22
<div class="email-section">
23
<a href="mailto:splitfire@setoelkahfi.se" class="email-link"> splitfire@setoelkahfi.se </a>
24
</div>
25
23
- <p>We'll do our best to respond to your inquiry as soon as possible.</p>
26
+ <p>{t('contact.response')}</p>
27
</div>
28
29
<div class="topics">
27
- <h2>What can you contact us about?</h2>
30
+ <h2>{t('contact.whatToAsk')}</h2>
31
<ul>
29
- <li>Questions about Javanese language learning materials</li>
30
- <li>Suggestions for new content or improvements</li>
31
- <li>Technical issues with the website</li>
32
- <li>Collaboration opportunities</li>
33
- <li>General feedback about PBJ Komplit!</li>
32
+ <li>{t('contact.question1')}</li>
33
+ <li>{t('contact.question2')}</li>
34
+ <li>{t('contact.question3')}</li>
35
+ <li>{t('contact.question4')}</li>
36
+ <li>{t('contact.question5')}</li>
37
</ul>
38
</div>
39
</div>
src/pages/en/contact.astro
new
+96
@@ -0,0 +1,96 @@
1
+---
2
+import IndexLayout from '@/layouts/IndexLayout.astro'
3
+import { SITE } from '@/config'
4
+import { getLangFromUrl, useTranslations } from '@/i18n/utils'
5
+
6
+const currentLang = getLangFromUrl(Astro.url)
7
+const t = useTranslations(currentLang)
8
+---
9
+
10
+<IndexLayout title={`${t('contact.title')} - ${SITE.title}`} description={t('contact.subtitle')}>
11
+ <div class="contact-page prose">
12
+ <h1>{t('contact.title')}</h1>
13
+
14
+ <p>
15
+ {t('contact.subtitle')}
16
+ </p>
17
+
18
+ <div class="contact-info">
19
+ <h2>{t('contact.getInTouch')}</h2>
20
+ <p>{t('contact.reachOut')}</p>
21
+
22
+ <div class="email-section">
23
+ <a href="mailto:splitfire@setoelkahfi.se" class="email-link"> splitfire@setoelkahfi.se </a>
24
+ </div>
25
+
26
+ <p>{t('contact.response')}</p>
27
+ </div>
28
+
29
+ <div class="topics">
30
+ <h2>{t('contact.whatToAsk')}</h2>
31
+ <ul>
32
+ <li>{t('contact.question1')}</li>
33
+ <li>{t('contact.question2')}</li>
34
+ <li>{t('contact.question3')}</li>
35
+ <li>{t('contact.question4')}</li>
36
+ <li>{t('contact.question5')}</li>
37
+ </ul>
38
+ </div>
39
+ </div>
40
+</IndexLayout>
41
+
42
+<style>
43
+ .contact-page {
44
+ max-width: 100%;
45
+ margin: 0 auto;
46
+ padding: 2rem 0;
47
+ }
48
+
49
+ .contact-info {
50
+ margin: 2rem 0;
51
+ }
52
+
53
+ .email-section {
54
+ margin: 1.5rem 0;
55
+ padding: 1rem;
56
+ background: var(--code-bg);
57
+ border-radius: 8px;
58
+ border-left: 4px solid var(--accent);
59
+ }
60
+
61
+ .email-link {
62
+ font-size: 1.25rem;
63
+ font-weight: 600;
64
+ color: var(--accent);
65
+ text-decoration: none;
66
+ word-break: break-all;
67
+ }
68
+
69
+ .email-link:hover {
70
+ text-decoration: underline;
71
+ }
72
+
73
+ .topics {
74
+ margin: 2rem 0;
75
+ }
76
+
77
+ .topics ul {
78
+ list-style-type: disc;
79
+ margin-left: 1.5rem;
80
+ }
81
+
82
+ .topics li {
83
+ margin: 0.5rem 0;
84
+ line-height: 1.6;
85
+ }
86
+
87
+ @media (max-width: 768px) {
88
+ .contact-page {
89
+ padding: 1rem 0;
90
+ }
91
+
92
+ .email-link {
93
+ font-size: 1.1rem;
94
+ }
95
+ }
96
+</style>
src/pages/en/index.astro
new
+18
@@ -0,0 +1,18 @@
1
+---
2
+import IndexLayout from '@/layouts/IndexLayout.astro'
3
+import About from '@/components/pages/About.astro'
4
+import PostList from '@/components/features/PostList.astro'
5
+import { SITE } from '@/config'
6
+import { getSortedFilteredPosts } from '@/utils/draft'
7
+
8
+const allPosts = await getSortedFilteredPosts()
9
+// Filter for English posts only
10
+const posts = allPosts.filter((post) => post.id.startsWith('en/'))
11
+---
12
+
13
+<IndexLayout title={SITE.title} description={SITE.description}>
14
+ <About />
15
+ <main>
16
+ <PostList posts={posts} />
17
+ </main>
18
+</IndexLayout>
src/pages/index.astro
+3
-1
@@ -5,7 +5,9 @@ import PostList from '@/components/features/PostList.astro'
5
import { SITE } from '@/config'
6
import { getSortedFilteredPosts } from '@/utils/draft'
7
8
-const posts = await getSortedFilteredPosts()
8
+const allPosts = await getSortedFilteredPosts()
9
+// Filter for Indonesian posts only (not starting with en/)
10
+const posts = allPosts.filter((post) => !post.id.startsWith('en/'))
11
---
12
13
<IndexLayout title={SITE.title} description={SITE.description}>