├── .gitignore ├── .npmrc ├── .prettierrc ├── LICENSE ├── README.md ├── assets └── css │ └── tailwind.css ├── components ├── LangSwitcher.vue ├── LocLink.vue └── Notification.vue ├── content └── blog │ ├── andor-rebellion │ ├── ar-eg.md │ └── en-ca.md │ └── mandalorian-second-season-recap │ ├── ar-eg.md │ └── en-ca.md ├── data └── show-data.ts ├── express-server.js ├── i18n.config.ts ├── i18n ├── datetime-formats.ts ├── number-formats.ts └── plurals.ts ├── lang ├── ar-EG.ts └── en-CA.ts ├── layouts └── default.vue ├── nuxt.config.ts ├── package-lock.json ├── package.json ├── pages ├── about.vue ├── blog │ ├── [slug].vue │ └── index.vue ├── index.vue ├── numbers.vue └── shows │ └── [id].vue ├── public ├── favicon.ico └── img │ ├── brand-logo.svg │ └── shows │ ├── andor.png │ ├── bad-batch.png │ ├── light-magic.png │ ├── mandalorian.png │ ├── obi-wan-kenobi.png │ └── tales-of-jedi.png ├── server └── api │ └── shows │ ├── [id].ts │ └── index.ts ├── tailwind.config.js ├── tsconfig.json └── utils └── html.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Phrase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 I18n Demo 2 | 3 | This is a Nuxt internationalization (i18n) demo app, companion to a Phrase blog 4 | tutorial. 5 | 6 | ## Setup 7 | 8 | Make sure to install the dependencies: 9 | 10 | ```bash 11 | # yarn 12 | yarn install 13 | 14 | # npm 15 | npm install 16 | 17 | # pnpm 18 | pnpm install 19 | ``` 20 | 21 | ## Development Server 22 | 23 | Start the development server on `http://localhost:3000` 24 | 25 | ```bash 26 | npm run dev 27 | ``` 28 | 29 | ## Production 30 | 31 | Build the application for production: 32 | 33 | ```bash 34 | npm run build 35 | ``` 36 | 37 | Locally preview production build: 38 | 39 | ```bash 40 | npm run preview 41 | ``` 42 | -------------------------------------------------------------------------------- /assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply bg-slate-800 text-purple-50; 7 | } 8 | 9 | h1 { 10 | @apply text-3xl font-extrabold mb-4; 11 | } 12 | -------------------------------------------------------------------------------- /components/LangSwitcher.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /components/LocLink.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /components/Notification.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /content/blog/andor-rebellion/ar-eg.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: بشأن أندور والثورة 3 | description: يسلط أندور الضوء على خطورة السلطة السياسية غير المحدودة وأهمية التصدي للقمع. 4 | locale: ar-eg 5 | --- 6 | 7 | تُستكشف سلسلة ستار وورز "أندور"، التي تدور أحداثها قبل أحداث فيلم "روغ وان: قصة حرب النجوم"، أصول ضابط الاستخبارات في تحالف الثوار كاسيان أندور ومشاركته في النضال ضد الإمبراطورية المجرية الطاغية. يستكشف المسلسل مواضيع الطغيان السياسي والتمرد، حيث نرى كيف يدفع كاسيان وشخصيات أخرى للقتال ضد نظام الإمبراطورية القمعي. 8 | 9 | من خلال تصويرها لتكتيكات الإمبراطورية اللا رحمة ونضال التمرد من أجل الحرية، تسلط سلسلة "أندور" الضوء على خطورة السلطة السياسية غير المحدودة وأهمية التصدي للقمع. في النهاية، تؤكد السلسلة الرسالة الخالدة بأن النضال من أجل الحرية والعدالة يتطلب يقظة مستمرة وتضحيات. 10 | 11 | — من كتابة [ChatGPT](https://chat.openai.com/) 12 | -------------------------------------------------------------------------------- /content/blog/andor-rebellion/en-ca.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: On Andor and Rebellion 3 | description: Andor highlights the dangers of unchecked political power and the importance of standing up against oppression. 4 | locale: en-ca 5 | --- 6 | 7 | The Star Wars show Andor, set before the events of Rogue One: A Star Wars Story, explores the origins of Rebel Alliance intelligence officer Cassian Andor and his involvement in the fight against the tyrannical Galactic Empire. The series delves into the themes of political tyranny and rebellion, as we see how Cassian and other characters are driven to fight against the Empire's oppressive regime. 8 | 9 | Through its portrayal of the Empire's ruthless tactics and the rebellion's desperate struggle for freedom, Andor highlights the dangers of unchecked political power and the importance of standing up against oppression. Ultimately, the show underscores the timeless message that the fight for freedom and justice requires constant vigilance and sacrifice. 10 | 11 | — written by [ChatGPT](https://chat.openai.com/) 12 | -------------------------------------------------------------------------------- /content/blog/mandalorian-second-season-recap/ar-eg.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ملخص للموسم الثاني من مسلسل ذا ماندالوريان 3 | description: تنبيه المفسد! 4 | locale: ar-eg 5 | --- 6 | 7 | يتابع الموسم الثاني من مسلسل ذا ماندالوريان مغامرات دين جارين (الماندالوريان) وسعيه لإعادة توحيد الطفل (المعروف أيضًا بـ "الطفل يودا") مع فرسان الجيداي. على طول الطريق، يواجه حلفاء جدد، مثل قوات الصدمة السابقة للثوار كارا دون، وشخصيات محبوبة من ثلاثية الأفلام الأصلية، بما في ذلك بوبا فت وآشوكا تانو. 8 | 9 | يتخلل الموسم مشاهد مليئة بالحركة والمغامرة، ولحظات مؤثرة، ومفترقات قصة مفاجئة تستكشف بشكل أعمق ثقافة الماندالوريان وتوسع في أسطورة كون ستار وورز. يتوج الموسم بمواجهة حاسمة بين الماندالوريان وموف جيديون، الذي يسعى لاستخدام قوى الطفل لأغراضه الشريرة. 10 | 11 | بشكل عام، يعد الموسم الثاني من مسلسل ذا ماندالوريان رحلة مثيرة تفي بتوقعات المعجبين وتقدم عناصر جديدة مثيرة لهذه السلسلة المحبوبة. 12 | 13 | — من كتابة [ChatGPT](https://chat.openai.com/) 14 | -------------------------------------------------------------------------------- /content/blog/mandalorian-second-season-recap/en-ca.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Recap of the Second Season of The Mandalorian 3 | description: Spoiler alert! 4 | locale: en-ca 5 | --- 6 | 7 | The second season of The Mandalorian follows the continuing adventures of Din Djarin (the Mandalorian) and his quest to reunite the Child (aka Baby Yoda) with the Jedi. Along the way, he encounters new allies, such as the skilled former Rebel shock trooper Cara Dune, and fan-favorite characters from the original trilogy, including Boba Fett and Ahsoka Tano. 8 | 9 | The season is filled with action-packed sequences, heartwarming moments, and surprising plot twists that further explore the Mandalorian culture and expand on the lore of the Star Wars universe. The finale culminates in an epic showdown between the Mandalorian and Moff Gideon, who seeks to use the Child's powers for his own nefarious purposes. 10 | 11 | Overall, the second season of The Mandalorian is a thrilling ride that delivers on both fan expectations and introduces exciting new elements to the beloved franchise. 12 | 13 | — written by [ChatGPT](https://chat.openai.com/) 14 | -------------------------------------------------------------------------------- /data/show-data.ts: -------------------------------------------------------------------------------- 1 | export const showData = [ 2 | { 3 | id: 1, 4 | img: "/img/shows/andor.png", 5 | premieredAt: "2022-09-21", 6 | episodeCount: 12, 7 | source: "https://en.wikipedia.org/wiki/Andor_(TV_series)", 8 | translations: { 9 | "en-CA": { 10 | name: "Andor", 11 | description: 12 | "Andor, also known as Star Wars: Andor, is an American science-fiction action-adventure limited series created by Tony Gilroy for the streaming service Disney+. It is the fourth live-action series in the Star Wars franchise, as well as a prequel to both the spin-off film Rogue One (2016) and the original Star Wars film (1977). The series follows thief-turned-Rebel spy Cassian Andor during the five years that lead to the events of the two aforementioned films.", 13 | }, 14 | "ar-EG": { 15 | name: "أندور", 16 | description: 17 | 'أندور، المعروف أيضًا باسم "ستار وورز: أندور"، هو مسلسل أمريكي قصير من الخيال العلمي و المغامرات، أنشأه توني جيلروي لخدمة البث المباشرDisney+. إنه المسلسل الرابع من المسلسلات الحية في سلسلة ستار وورز، وهو أيضًا سابقة لكل من الفيلم الفرعي روج وان (2016) وفيلم ستار وورز الأصلي (1977). يتتبع المسلسل اللص المتحول إلى جاسوس متمرد كاسيان أندور خلال السنوات الخمس التي تسبق أحداث الفيلمين المذكورين.', 18 | }, 19 | }, 20 | }, 21 | { 22 | id: 2, 23 | img: "/img/shows/mandalorian.png", 24 | premieredAt: "2019-11-12", 25 | episodeCount: 23, 26 | source: "https://en.wikipedia.org/wiki/The_Mandalorian", 27 | translations: { 28 | "en-CA": { 29 | name: "The Mandalorian", 30 | description: 31 | "The Mandalorian is an American space Western television series created by Jon Favreau for the streaming service Disney+. It is the first live-action series in the Star Wars franchise, beginning five years after the events of Return of the Jedi (1983), and stars Pedro Pascal as the title character, a lone bounty hunter who goes on the run to protect the Force-sensitive child Grogu. Starting in the third season, Katee Sackhoff also stars as Mandalorian Bo-Katan Kryze, following her appearance in the second season.", 32 | }, 33 | "ar-EG": { 34 | name: "الماندالوريان", 35 | description: 36 | 'ذا ماندالوريان هو مسلسل تلفزيوني أمريكي من نوع الغرب الفضائي، أنشأه جون فافرو لخدمة البث Disney+. إنه المسلسل الأول من المسلسلات الحية في سلسلة ستار وورز، ويبدأ خمس سنوات بعد أحداث عودة الجيداي (1983). يتمثل بطل العمل بيدرو باسكال في شخصية الماندالوريان، صائد الجوائز الوحيد الذي يهرب لحماية الطفل غروغو المُحسّ بالـ"قوة". ابتداءً من الموسم الثالث، تظهر كاتي ساكوف أيضًا في دور الماندالوريان بو-كاتان كرايز بعد ظهورها في الموسم الثاني.', 37 | }, 38 | }, 39 | }, 40 | { 41 | id: 3, 42 | img: "/img/shows/bad-batch.png", 43 | premieredAt: "2021-05-04", 44 | episodeCount: 32, 45 | source: "https://en.wikipedia.org/wiki/Star_Wars:_The_Bad_Batch", 46 | 47 | translations: { 48 | "en-CA": { 49 | name: "Star Wars: The Bad Batch ", 50 | description: 51 | "Star Wars: The Bad Batch is an American animated series created by Dave Filoni for the streaming service Disney+. It is part of the Star Wars franchise, acting as both a sequel to, and spin-off from, the series Star Wars: The Clone Wars. The Bad Batch is produced by Lucasfilm Animation, with Jennifer Corbett as head writer and Brad Rau as supervising director.", 52 | }, 53 | "ar-EG": { 54 | name: "ستار وورز: ذا باد باتش", 55 | description: 56 | "ستار وورز: ذا باد باتش هو مسلسل رسوم متحركة أمريكي أنشأه ديف فيلوني لخدمة البث Disney+. إنه جزء من سلسلة ستار وورز، حيث يعمل ككل من الجزء اللاحق والفرعي لمسلسل حرب النجوم: حروب المستنسخين. يتم إنتاج ذا باد باتش من قبل شركة لوكاس فيلم آنيميشن، بينما تتولى جينيفر كوربت دور كاتبة السيناريو الرئيسية وبراد راو دور المخرج المشرف.", 57 | }, 58 | }, 59 | }, 60 | { 61 | id: 4, 62 | img: "/img/shows/tales-of-jedi.png", 63 | premieredAt: "2022-10-26", 64 | episodeCount: 6, 65 | source: "https://en.wikipedia.org/wiki/Tales_of_the_Jedi_(TV_series)", 66 | translations: { 67 | "en-CA": { 68 | name: "Tales of the Jedi", 69 | description: 70 | "Tales of the Jedi, also known as Star Wars: Tales of the Jedi, is an American animated anthology series created by Dave Filoni. It is part of the Star Wars franchise, exploring different Jedi characters from the prequel trilogy era. The series was produced by Lucasfilm Animation, with Charles Murray as head writer and Filoni as supervising director, for the streaming service Disney+.", 71 | }, 72 | "ar-EG": { 73 | name: "قصص الجيدَي", 74 | description: 75 | "قصص الجيدَي، المعروفة أيضًا باسم ستار وورز: قصص الجيدي، هي سلسلة رسوم متحركة أمريكية مجموعة من القصص المستقلة التي أنشأها ديف فيلوني. إنها جزء من سلسلة ستار وورز، وتستكشف شخصيات جيدَي مختلفة من عصر ثلاثية الأفلام السابقة. تم إنتاج المسلسل بواسطة شركة لوكاس فيلم آنيميشن، مع تشارلز موراي في دور كاتب السيناريو الرئيسي وفيلوني في دور المخرج المشرف، لخدمة البث Disney+.", 76 | }, 77 | }, 78 | }, 79 | { 80 | id: 5, 81 | img: "/img/shows/obi-wan-kenobi.png", 82 | premieredAt: "2022-05-27", 83 | episodeCount: 6, 84 | source: "https://en.wikipedia.org/wiki/Obi-Wan_Kenobi_(TV_series)", 85 | translations: { 86 | "en-CA": { 87 | name: "Obi-Wan Kenobi", 88 | description: 89 | "Obi-Wan Kenobi is an American television miniseries created for the streaming service Disney+. It is part of the Star Wars franchise and stars Ewan McGregor as the title character, reprising his role from the Star Wars prequel trilogy. Set ten years after the events of Star Wars: Episode III – Revenge of the Sith (2005), the series follows Kenobi as he sets out to rescue the kidnapped Princess Leia (Vivien Lyra Blair) from the Galactic Empire, leading to a confrontation with his former apprentice, Darth Vader (Hayden Christensen).", 90 | }, 91 | "ar-EG": { 92 | name: "أوبي وان كينوبي", 93 | description: 94 | "أوبي وان كينوبي هو مسلسل تلفزيوني أمريكي قصير تم إنشاؤه لخدمة البث Disney+. إنه جزء من سلسلة ستار وورز ويتميز بمشاركة إيوان مكريجور في دور الشخصية الرئيسية، حيث يعيد تجسيد دوره من ثلاثية أفلام ستار وورز سابقة الأحداث. يجري المسلسل عشر سنوات بعد أحداث فيلم ستار وورز: الحلقة الثالثة - انتقام السيث (2005)، ويتتبع كينوبي في مهمته لإنقاذ الأميرة ليا (فيفيان ليرا بلاير) المختطفة من الإمبراطورية المجرية، مما يؤدي إلى مواجهة مع تلميذه السابق، دارث فيدر (هايدن كريستنسن).", 95 | }, 96 | }, 97 | }, 98 | { 99 | id: 6, 100 | img: "/img/shows/light-magic.png", 101 | premieredAt: "2022-07-27", 102 | episodeCount: 6, 103 | source: "https://en.wikipedia.org/wiki/Light_%26_Magic_(TV_series)", 104 | translations: { 105 | "en-CA": { 106 | name: "Light & Magic", 107 | description: 108 | "Light & Magic is a 2022 documentary TV series directed by Lawrence Kasdan and produced by Lucasfilm Ltd. and Imagine Documentaries. The series debuted as a Disney+ Original on July 27, 2022, with all six episodes airing at once.", 109 | }, 110 | "ar-EG": { 111 | name: "لايت آند ماجيك", 112 | description: 113 | '"لايت آند ماجيك" هو مسلسل وثائقي تلفزيوني صدر في عام 2022 وتولى إخراجه لورنس كاسدان وإنتاجه من قبل شركة لوكاس فيلم المحدودة وشركة إيماجين للأفلام الوثائقية. تم عرض المسلسل لأول مرة كفيلم أصلي على خدمة البث Disney+ في 27 يوليو 2022، وتم عرض الحلقات الستة مرة واحدة.', 114 | }, 115 | }, 116 | }, 117 | ] 118 | -------------------------------------------------------------------------------- /express-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ⚠️ This server is not production-ready! It has no security! 3 | * It is a simple demo to show how a server could be used with Nuxt's 4 | * static site generation (SSG). 5 | * 6 | * See: https://nuxt.com/docs/getting-started/deployment#static-hosting 7 | */ 8 | const express = require("express") 9 | const app = express() 10 | const path = require("path") 11 | 12 | const port = process.env.PORT || 4000 13 | 14 | app.use(express.static("dist")) 15 | 16 | app.get("*", (_req, res) => { 17 | res.sendFile(path.resolve(__dirname, "dist", "en-CA", "index.html")) 18 | }) 19 | 20 | app.listen(port, () => 21 | console.log(`Server is running on: http://localhost:${port}`) 22 | ) 23 | -------------------------------------------------------------------------------- /i18n.config.ts: -------------------------------------------------------------------------------- 1 | import { arabicPlurals } from "./i18n/plurals" 2 | import { numberFormats } from "./i18n/number-formats" 3 | import { datetimeFormats } from "./i18n/datetime-formats" 4 | 5 | export default defineI18nConfig(() => ({ 6 | legacy: false, 7 | fallbackLocale: "en-CA", 8 | pluralRules: { 9 | "ar-EG": arabicPlurals, 10 | }, 11 | numberFormats, 12 | datetimeFormats, 13 | })) 14 | -------------------------------------------------------------------------------- /i18n/datetime-formats.ts: -------------------------------------------------------------------------------- 1 | export const datetimeFormats = { 2 | "en-CA": { 3 | short: { 4 | year: "numeric", 5 | month: "short", 6 | day: "numeric", 7 | }, 8 | }, 9 | "ar-EG": { 10 | short: { 11 | year: "numeric", 12 | month: "2-digit", 13 | day: "numeric", 14 | }, 15 | }, 16 | } as const 17 | -------------------------------------------------------------------------------- /i18n/number-formats.ts: -------------------------------------------------------------------------------- 1 | export const numberFormats = { 2 | "en-CA": { 3 | currency: { 4 | style: "currency", 5 | currency: "USD", 6 | notation: "standard", 7 | }, 8 | decimal: { 9 | style: "decimal", 10 | minimumFractionDigits: 2, 11 | maximumFractionDigits: 2, 12 | }, 13 | percent: { 14 | style: "percent", 15 | useGrouping: false, 16 | }, 17 | }, 18 | "ar-EG": { 19 | currency: { 20 | style: "currency", 21 | currency: "USD", 22 | notation: "standard", 23 | }, 24 | decimal: { 25 | style: "decimal", 26 | minimumFractionDigits: 2, 27 | maximumFractionDigits: 2, 28 | }, 29 | percent: { 30 | style: "percent", 31 | useGrouping: false, 32 | }, 33 | }, 34 | } as const 35 | -------------------------------------------------------------------------------- /i18n/plurals.ts: -------------------------------------------------------------------------------- 1 | export const arabicPlurals = (choice: number): number => { 2 | const name = new Intl.PluralRules("ar-EG").select(choice) 3 | 4 | return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name] 5 | } 6 | -------------------------------------------------------------------------------- /lang/ar-EG.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | app_title: "أندورَبل", 3 | nav: { 4 | shows: "برامج", 5 | blog: "المدونة", 6 | about: "نبذة عنا", 7 | }, 8 | home: { 9 | title: "أندورَبل - برامج حرب النجوم", 10 | header: "يُبَث حالياً", 11 | meta: { 12 | description: 13 | "أندورَبل هو قائمة مختارة ومدوّنة تتحدث عن عروض ستار وورز على البث المباشر.", 14 | }, 15 | }, 16 | about: { 17 | title: "نبذة عنا", 18 | body: "أندورَبل هو موقع عن كل شيء متعلق بعروض بث حرب النجوم. بدأ الموقع كمشروع شغف ونما إلى ما هو عليه اليوم.", 19 | }, 20 | show: { 21 | episodes: ` 22 | لا توجد حلقات 23 | | 24 | حلقة {n} 25 | | 26 | حلقتين {n} 27 | | 28 | {n} حلقات 29 | | 30 | {n} حلقة 31 | | 32 | {n} حلقة 33 | `, 34 | img_alt: "صورة {show}", 35 | premiered_at: "تم عرضه في {date}", 36 | from_wikipedia: "من ويكيبيديا", 37 | }, 38 | blog: { 39 | title: "من المدونة", 40 | }, 41 | numbers: { 42 | title: "أرقام", 43 | currency: "عملة: {number}", 44 | decimal: "عشري: {number}", 45 | percent: "نسبة مئوية: {number}", 46 | }, 47 | notifications: { 48 | new_episode: "أهلاً {username}، حلقة جديدة من {show} نزلت!", 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /lang/en-CA.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | app_title: "Andorable", 3 | nav: { 4 | shows: "Shows", 5 | blog: "Blog", 6 | about: "About", 7 | }, 8 | home: { 9 | title: "Andorable - Star Wars Shows", 10 | header: "Currently Streaming", 11 | meta: { 12 | description: 13 | "Andorable is a curated list and blog all about streaming Star Wars shows.", 14 | }, 15 | }, 16 | about: { 17 | title: "About", 18 | body: "Andorable is all about streaming Star Wars shows. The site started as a passion project and grew into what it is today.", 19 | }, 20 | show: { 21 | episodes: "{n} episode | {n} episodes", 22 | img_alt: "Image for {show}", 23 | premiered_at: "Premiered at {date}", 24 | from_wikipedia: "From Wikipedia", 25 | }, 26 | blog: { 27 | title: "From the Blog", 28 | }, 29 | numbers: { 30 | title: "Numbers", 31 | currency: "Currency: {number}", 32 | decimal: "Decimal: {number}", 33 | percent: "Percent: {number}", 34 | }, 35 | notifications: { 36 | new_episode: "Hello, {username}. A new episode of {show} just landed!", 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 63 | 64 | 70 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | 3 | export default defineNuxtConfig({ 4 | modules: ["@nuxtjs/tailwindcss", "@nuxt/content", "@nuxtjs/i18n"], 5 | routeRules: { 6 | "/": { redirect: "/en-CA" }, 7 | }, 8 | i18n: { 9 | baseUrl: "http://localhost:3000", 10 | locales: [ 11 | { 12 | code: "en-CA", 13 | name: "English", 14 | iso: "en-CA", 15 | dir: "ltr", 16 | file: "en-CA.ts", 17 | }, 18 | { 19 | code: "ar-EG", 20 | name: "العربية (Arabic)", 21 | iso: "ar-EG", 22 | dir: "rtl", 23 | file: "ar-EG.ts", 24 | }, 25 | ], 26 | lazy: true, 27 | langDir: "lang", 28 | strategy: "prefix", 29 | defaultLocale: "en-CA", 30 | detectBrowserLanguage: false, 31 | vueI18n: "./i18n.config.ts", 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare" 10 | }, 11 | "devDependencies": { 12 | "@nuxt/content": "^2.6.0", 13 | "@nuxtjs/i18n": "^8.0.0-beta.12", 14 | "@nuxtjs/tailwindcss": "^6.7.0", 15 | "@tailwindcss/typography": "^0.5.9", 16 | "express": "^4.18.2", 17 | "nuxt": "^3.5.1", 18 | "prettier": "^2.8.8", 19 | "prettier-plugin-tailwindcss": "^0.3.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages/about.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /pages/blog/[slug].vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /pages/blog/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 38 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 35 | -------------------------------------------------------------------------------- /pages/numbers.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /pages/shows/[id].vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/favicon.ico -------------------------------------------------------------------------------- /public/img/brand-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/img/shows/andor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/img/shows/andor.png -------------------------------------------------------------------------------- /public/img/shows/bad-batch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/img/shows/bad-batch.png -------------------------------------------------------------------------------- /public/img/shows/light-magic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/img/shows/light-magic.png -------------------------------------------------------------------------------- /public/img/shows/mandalorian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/img/shows/mandalorian.png -------------------------------------------------------------------------------- /public/img/shows/obi-wan-kenobi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/img/shows/obi-wan-kenobi.png -------------------------------------------------------------------------------- /public/img/shows/tales-of-jedi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhraseApp-Blog/nuxt-i18n-2023/850fe0cc2560555b2a0701c434a7bb658be63567/public/img/shows/tales-of-jedi.png -------------------------------------------------------------------------------- /server/api/shows/[id].ts: -------------------------------------------------------------------------------- 1 | import { showData as shows } from "../../../data/show-data" 2 | 3 | export default defineEventHandler(async (event) => { 4 | const id = +event.context.params!.id 5 | const { locale } = getQuery(event) 6 | 7 | const { translations, ...others } = shows.find((show) => show.id === id)! 8 | 9 | return { ...others, ...translations[locale as keyof typeof translations] } 10 | }) 11 | -------------------------------------------------------------------------------- /server/api/shows/index.ts: -------------------------------------------------------------------------------- 1 | import { showData as shows } from "../../../data/show-data" 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { locale } = getQuery(event) 5 | 6 | return shows.map(({ translations, ...others }) => ({ 7 | ...others, 8 | ...translations[locale as keyof typeof translations], 9 | })) 10 | }) 11 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("@tailwindcss/typography")], 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | } 5 | -------------------------------------------------------------------------------- /utils/html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a full HTML title, appending the site name 3 | * to the page title given. 4 | * 5 | * @param pageTitle HTML title of the current page 6 | * @returns Full HTML title, including localized site name 7 | */ 8 | export const title = (pageTitle: string): string => { 9 | const { t } = useI18n() 10 | 11 | return `${pageTitle} | ${t("app_title")}` 12 | } 13 | 14 | /** 15 | * Returns a translated page HTML title. You must declare 16 | * the key/value pair in your translation messages. This 17 | * function appends the localized site name to the title. 18 | * 19 | * @param locKey translation key 20 | * @returns the localized HTML title, including site name 21 | */ 22 | export const localizedTitle = (locKey: string): string => { 23 | const { t } = useI18n() 24 | const localizedPageTitle = t(locKey) 25 | 26 | return title(localizedPageTitle) 27 | } 28 | --------------------------------------------------------------------------------