diff --git a/astro.config.mjs b/astro.config.mjs
index d2c210c..458ba56 100755
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -7,12 +7,26 @@ import { defineConfig, squooshImageService } from "astro/config";
import remarkCollapse from "remark-collapse";
import remarkToc from "remark-toc";
import config from "./src/config/config.json";
+import languagesJSON from "./src/config/language.json";
+const { default_language } = config.settings;
+
+const supportedLang = [...languagesJSON.map((lang) => lang.languageCode)];
+const disabledLanguages = config.settings.disable_languages;
+
+// Filter out disabled languages from supportedLang
+const filteredSupportedLang = supportedLang.filter(
+ (lang) => !disabledLanguages.includes(lang),
+);
// https://astro.build/config
export default defineConfig({
site: config.site.base_url ? config.site.base_url : "http://examplesite.com",
base: config.site.base_path ? config.site.base_path : "/",
- trailingSlash: config.site.trailing_slash ? "always" : "never",
+ trailingSlash: config.site.trailing_slash ? "always" : "ignore",
+ i18n: {
+ locales: filteredSupportedLang,
+ defaultLocale: default_language,
+ },
image: {
service: squooshImageService(),
},
diff --git a/package.json b/package.json
index 5f30638..2581fc6 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "astroplate",
- "version": "3.4.5",
+ "version": "4.0.0",
"description": "Astro and Tailwindcss boilerplate",
"author": "zeon.studio",
"license": "MIT",
@@ -11,50 +11,51 @@
"preview": "astro preview",
"format": "prettier -w ./src",
"generate-json": "node scripts/jsonGenerator.js",
- "remove-darkmode": "node scripts/removeDarkmode.js && yarn format"
+ "remove-darkmode": "node scripts/removeDarkmode.js && yarn format",
+ "remove-multilang": "node scripts/removeMultilang.js && yarn format"
},
"dependencies": {
- "@astrojs/mdx": "^2.3.0",
- "@astrojs/react": "^3.3.0",
- "@astrojs/rss": "^4.0.5",
- "@astrojs/sitemap": "^3.1.2",
+ "@astrojs/mdx": "^3.1.3",
+ "@astrojs/react": "^3.6.1",
+ "@astrojs/rss": "^4.0.7",
+ "@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
- "astro": "^4.6.1",
+ "astro": "^4.12.3",
"astro-auto-import": "^0.4.2",
- "astro-font": "^0.0.80",
+ "astro-font": "^0.0.81",
"date-fns": "^3.6.0",
"disqus-react": "^1.1.5",
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
- "marked": "^12.0.1",
- "prettier-plugin-astro": "^0.13.0",
- "prettier-plugin-tailwindcss": "^0.5.13",
+ "marked": "^13.0.3",
+ "prettier-plugin-astro": "^0.14.1",
+ "prettier-plugin-tailwindcss": "^0.6.5",
"prop-types": "^15.8.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-icons": "^5.0.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.2.1",
"react-lite-youtube-embed": "^2.4.0",
"remark-collapse": "^0.1.2",
"remark-toc": "^9.0.0",
- "swiper": "^11.1.1"
+ "swiper": "^11.1.8"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.7",
- "@tailwindcss/typography": "^0.5.12",
+ "@tailwindcss/typography": "^0.5.13",
"@types/marked": "^5.0.2",
- "@types/node": "20.12.7",
- "@types/react": "18.2.78",
- "@types/react-dom": "18.2.25",
+ "@types/node": "22.0.0",
+ "@types/react": "18.3.3",
+ "@types/react-dom": "18.3.0",
"autoprefixer": "^10.4.19",
- "eslint": "^9.0.0",
- "postcss": "^8.4.38",
- "prettier": "^3.2.5",
- "prettier-plugin-astro": "^0.13.0",
- "prettier-plugin-tailwindcss": "^0.5.13",
- "sass": "^1.75.0",
- "sharp": "0.33.1",
+ "eslint": "^9.8.0",
+ "postcss": "^8.4.40",
+ "prettier": "^3.3.3",
+ "prettier-plugin-astro": "^0.14.1",
+ "prettier-plugin-tailwindcss": "^0.6.5",
+ "sass": "^1.77.8",
+ "sharp": "0.33.4",
"tailwind-bootstrap-grid": "^5.1.0",
- "tailwindcss": "^3.4.3",
- "typescript": "5.4.5"
+ "tailwindcss": "^3.4.7",
+ "typescript": "5.5.4"
}
}
diff --git a/readme.md b/readme.md
index a9badb4..ab5c00c 100755
--- a/readme.md
+++ b/readme.md
@@ -6,7 +6,7 @@
If you find this project useful, please give it a â to show your support.
-
@@ -26,6 +26,7 @@
## đ Key Features
- đ„ Multi-Authors
+- đ Multilingual
- đŻ Similar Posts Suggestion
- đ Search Functionality
- đ Dark Mode
@@ -65,10 +66,10 @@
### đŠ Dependencies
-- astro 4.0+
+- astro v4.12+
- node v20.10+
- npm v10.2+
-- tailwind v3.3+
+- tailwind v3.4+
### đ Install Dependencies
@@ -109,12 +110,14 @@ docker run -it --rm astroplate ash
```
+
## đ Reporting Issues
We use GitHub Issues as the official bug tracker for this Template. Please Search [existing issues](https://github.com/zeon-studio/astroplate/issues). Itâs possible someone has already reported the same problem.
If your problem or idea has not been addressed yet, feel free to [open a new issue](https://github.com/zeon-studio/astroplate/issues).
+
## đ License
Copyright (c) 2023 - Present, Designed & Developed by [Zeon Studio](https://zeon.studio/)
diff --git a/scripts/jsonGenerator.js b/scripts/jsonGenerator.js
index 8aee1ac..5881da2 100644
--- a/scripts/jsonGenerator.js
+++ b/scripts/jsonGenerator.js
@@ -1,49 +1,66 @@
const fs = require("fs");
const path = require("path");
const matter = require("gray-matter");
+const languages = require("../src/config/language.json");
-const CONTENT_DEPTH = 2;
const JSON_FOLDER = "./.json";
-const BLOG_FOLDER = "src/content/blog";
+const CONTENT_ROOT = "src/content";
+const CONTENT_DEPTH = 3;
+const BLOG_FOLDER = "blog";
// get data from markdown
-const getData = (folder, groupDepth) => {
- const getPath = fs.readdirSync(folder);
- const removeIndex = getPath.filter((item) => !item.startsWith("-"));
+const getData = (folder, groupDepth, langIndex = 0) => {
+ const getPaths = languages
+ .map((lang, index) => {
+ const langFolder = lang.contentDir ? lang.contentDir : lang.languageCode;
+ const dir = path.join(CONTENT_ROOT, folder, langFolder);
+ return fs
+ .readdirSync(dir)
+ .filter(
+ (filename) =>
+ !filename.startsWith("-") &&
+ (filename.endsWith(".md") || filename.endsWith(".mdx")),
+ )
+ .flatMap((filename) => {
+ const filepath = path.join(dir, filename);
+ const stats = fs.statSync(filepath);
+ const isFolder = stats.isDirectory();
- const getPaths = removeIndex.flatMap((filename) => {
- const filepath = path.join(folder, filename);
- const stats = fs.statSync(filepath);
- const isFolder = stats.isDirectory();
+ if (isFolder) {
+ return getData(filepath, groupDepth, index);
+ } else {
+ const file = fs.readFileSync(filepath, "utf-8");
+ const { data, content } = matter(file);
+ const pathParts = filepath.split(path.sep);
- if (isFolder) {
- return getData(filepath, groupDepth);
- } else if (filename.endsWith(".md") || filename.endsWith(".mdx")) {
- const file = fs.readFileSync(filepath, "utf-8");
- const { data, content } = matter(file);
- const pathParts = filepath.split(path.sep);
- const slug =
- data.slug ||
- pathParts
- .slice(CONTENT_DEPTH)
- .join("/")
- .replace(/\.[^/.]+$/, "");
- const group = pathParts[groupDepth];
+ let slug;
+ if (data.slug) {
+ const slugParts = data.slug.split("/");
+ slugParts[0] = BLOG_FOLDER;
+ slug = slugParts.join("/");
+ } else {
+ slug = pathParts
+ .slice(CONTENT_DEPTH)
+ .join("/")
+ .replace(/\.[^/.]+$/, "");
+ slug = `${BLOG_FOLDER}/${slug.split("/").slice(1).join("/")}`;
+ }
+ data.slug = slug;
+ const group = "blog";
- return {
- group: group,
- slug: slug,
- frontmatter: data,
- content: content,
- };
- } else {
- return [];
- }
- });
+ return {
+ lang: languages[index].languageCode, // Set the correct language code dynamically
+ group: group,
+ slug: data.slug,
+ frontmatter: data,
+ content: content,
+ };
+ }
+ });
+ })
+ .flat();
- const publishedPages = getPaths.filter(
- (page) => !page.frontmatter?.draft && page,
- );
+ const publishedPages = getPaths.filter((page) => !page.frontmatter?.draft);
return publishedPages;
};
@@ -56,10 +73,10 @@ try {
// create json files
fs.writeFileSync(
`${JSON_FOLDER}/posts.json`,
- JSON.stringify(getData(BLOG_FOLDER, 2)),
+ JSON.stringify(getData(BLOG_FOLDER, 3)),
);
- // merger json files for search
+ // merge json files for search
const posts = require(`../${JSON_FOLDER}/posts.json`);
const search = [...posts];
fs.writeFileSync(`${JSON_FOLDER}/search.json`, JSON.stringify(search));
diff --git a/scripts/removeMultilang.js b/scripts/removeMultilang.js
new file mode 100644
index 0000000..d885069
--- /dev/null
+++ b/scripts/removeMultilang.js
@@ -0,0 +1,72 @@
+const fs = require("fs");
+const path = require("path");
+const languages = require("../src/config/language.json");
+
+// Filter out the English language
+const englishLang = languages.filter((item) => item.languageCode === "en");
+const filterLangs = languages.filter((item) => item.languageCode !== "en");
+const contentDir = "src/content";
+const configDir = "src/config";
+const i18nDir = "src/i18n";
+
+// Update language.json to only include the English language
+fs.writeFileSync(
+ path.join(configDir, "language.json"),
+ JSON.stringify(englishLang, null, 2),
+);
+
+// Remove content directories for languages other than English
+filterLangs.forEach((lang) => {
+ const langContentDir = path.join(contentDir, lang.contentDir);
+ fs.rm(langContentDir, { recursive: true, force: true }, (err) => {
+ if (err) {
+ console.error(`Error deleting folder ${langContentDir}:`, err);
+ return;
+ }
+ console.log(`Folder ${langContentDir} deleted successfully`);
+ });
+});
+
+// Remove other menu.{lang}.json files except menu.en.json
+fs.readdir(configDir, (err, files) => {
+ if (err) {
+ console.error("Error reading config directory:", err);
+ return;
+ }
+
+ files.forEach((file) => {
+ if (file.startsWith("menu.") && file !== "menu.en.json") {
+ const filePath = path.join(configDir, file);
+ fs.unlink(filePath, (err) => {
+ if (err) {
+ console.error(`Error deleting file ${filePath}:`, err);
+ return;
+ }
+ console.log(`File ${filePath} deleted successfully`);
+ });
+ }
+ });
+});
+
+// Remove other language files from i18n folder except en.json
+fs.readdir(i18nDir, (err, files) => {
+ if (err) {
+ console.error("Error reading i18n directory:", err);
+ return;
+ }
+
+ files.forEach((file) => {
+ if (file !== "en.json") {
+ const filePath = path.join(i18nDir, file);
+ fs.unlink(filePath, (err) => {
+ if (err) {
+ console.error(`Error deleting file ${filePath}:`, err);
+ return;
+ }
+ console.log(`File ${filePath} deleted successfully`);
+ });
+ }
+ });
+});
+
+console.log("Cleanup completed.");
diff --git a/src/config/config.json b/src/config/config.json
index 683fe50..3b3d910 100755
--- a/src/config/config.json
+++ b/src/config/config.json
@@ -19,7 +19,10 @@
"default_theme": "system",
"pagination": 2,
"summary_length": 200,
- "blog_folder": "blog"
+ "blog_folder": "blog",
+ "default_language": "en",
+ "disable_languages": [],
+ "default_language_in_subdir": false
},
"params": {
diff --git a/src/config/language.json b/src/config/language.json
new file mode 100644
index 0000000..706b137
--- /dev/null
+++ b/src/config/language.json
@@ -0,0 +1,14 @@
+[
+ {
+ "languageName": "En",
+ "languageCode": "en",
+ "contentDir": "english",
+ "weight": 1
+ },
+ {
+ "languageName": "Fr",
+ "languageCode": "fr",
+ "contentDir": "french",
+ "weight": 2
+ }
+]
diff --git a/src/config/menu.en.json b/src/config/menu.en.json
new file mode 100644
index 0000000..9f52c3a
--- /dev/null
+++ b/src/config/menu.en.json
@@ -0,0 +1,24 @@
+{
+ "main": [
+ { "name": "Home", "url": "/" },
+ { "name": "About", "url": "/about" },
+ { "name": "Elements", "url": "/elements" },
+ {
+ "name": "Pages",
+ "url": "",
+ "hasChildren": true,
+ "children": [
+ { "name": "Contact", "url": "/contact" },
+ { "name": "Blog", "url": "/blog" },
+ { "name": "Authors", "url": "/authors" },
+ { "name": "Categories", "url": "/categories" },
+ { "name": "Tags", "url": "/tags" },
+ { "name": "404 Page", "url": "/404" }
+ ]
+ }
+ ],
+ "footer": [
+ { "name": "Elements", "url": "/elements" },
+ { "name": "Privacy Policy", "url": "/privacy-policy" }
+ ]
+}
diff --git a/src/config/menu.fr.json b/src/config/menu.fr.json
new file mode 100644
index 0000000..495c883
--- /dev/null
+++ b/src/config/menu.fr.json
@@ -0,0 +1,24 @@
+{
+ "main": [
+ { "name": "Accueil", "url": "/" },
+ { "name": "Ă propos", "url": "/about" },
+ { "name": "ĂlĂ©ments", "url": "/elements" },
+ {
+ "name": "Pages",
+ "url": "",
+ "hasChildren": true,
+ "children": [
+ { "name": "Contact", "url": "/contact" },
+ { "name": "Blog", "url": "/blog" },
+ { "name": "Auteurs", "url": "/authors" },
+ { "name": "Catégories", "url": "/categories" },
+ { "name": "Ătiquettes", "url": "/tags" },
+ { "name": "Page 404", "url": "/404" }
+ ]
+ }
+ ],
+ "footer": [
+ { "name": "ĂlĂ©ments", "url": "/elements" },
+ { "name": "Politique de confidentialité", "url": "/privacy-policy" }
+ ]
+}
diff --git a/src/config/menu.json b/src/config/menu.json
deleted file mode 100755
index 3502218..0000000
--- a/src/config/menu.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
- "main": [
- {
- "name": "Home",
- "url": "/"
- },
- {
- "name": "About",
- "url": "/about"
- },
- {
- "name": "Elements",
- "url": "/elements"
- },
- {
- "name": "Pages",
- "url": "",
- "hasChildren": true,
- "children": [
- {
- "name": "Contact",
- "url": "/contact"
- },
- {
- "name": "Blog",
- "url": "/blog"
- },
- {
- "name": "Authors",
- "url": "/authors"
- },
- {
- "name": "Categories",
- "url": "/categories"
- },
- {
- "name": "Tags",
- "url": "/tags"
- },
- {
- "name": "404 Page",
- "url": "/404"
- }
- ]
- }
- ],
- "footer": [
- {
- "name": "Elements",
- "url": "/elements"
- },
- {
- "name": "Privacy Policy",
- "url": "/privacy-policy"
- }
- ]
-}
diff --git a/src/content/about/-index.md b/src/content/about/english/-index.md
similarity index 100%
rename from src/content/about/-index.md
rename to src/content/about/english/-index.md
diff --git a/src/content/about/french/-index.md b/src/content/about/french/-index.md
new file mode 100644
index 0000000..041c6ad
--- /dev/null
+++ b/src/content/about/french/-index.md
@@ -0,0 +1,9 @@
+---
+title: "Hé, je suis John Doe !"
+meta_title: "About"
+description: "this is meta description"
+image: "/images/avatar.png"
+draft: false
+---
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Ils ne connaissent pas les bienfaits du corps, ou sauf qu'ils le recevront Ă d'autres moments, le tout, les temps de travail, qu'il dĂ©teste Ă partir de ce moment mais. Il fuit les plaisirs perçus pour ĂȘtre supposĂ©s n'ĂȘtre rien, tout ou, et la douleur est ce qui est la plus grande option, car cela lui est facile, et avec ce que cela s'ensuit, ils lui procurent de la douleur et ainsi de suite ! Car ledit expĂ©dient de la vĂ©ritĂ© repousse le plaisir et empĂȘche la douleur, ils fournissent comme si
diff --git a/src/content/authors/-index.md b/src/content/authors/english/-index.md
similarity index 100%
rename from src/content/authors/-index.md
rename to src/content/authors/english/-index.md
diff --git a/src/content/authors/john-doe.md b/src/content/authors/english/john-doe.md
similarity index 100%
rename from src/content/authors/john-doe.md
rename to src/content/authors/english/john-doe.md
diff --git a/src/content/authors/sam-wilson.md b/src/content/authors/english/sam-wilson.md
similarity index 100%
rename from src/content/authors/sam-wilson.md
rename to src/content/authors/english/sam-wilson.md
diff --git a/src/content/authors/william-jacob.md b/src/content/authors/english/william-jacob.md
similarity index 100%
rename from src/content/authors/william-jacob.md
rename to src/content/authors/english/william-jacob.md
diff --git a/src/content/authors/french/-index.md b/src/content/authors/french/-index.md
new file mode 100644
index 0000000..517a9bf
--- /dev/null
+++ b/src/content/authors/french/-index.md
@@ -0,0 +1,5 @@
+---
+title: "Auteurs"
+meta_title: ""
+description: "this is meta description"
+---
diff --git a/src/content/authors/french/john-doe.md b/src/content/authors/french/john-doe.md
new file mode 100644
index 0000000..857f439
--- /dev/null
+++ b/src/content/authors/french/john-doe.md
@@ -0,0 +1,20 @@
+---
+title: John Doe
+email: johndoe@email.com
+image: "/images/avatar.png"
+description: this is meta description
+social:
+ - name: github
+ icon: FaGithub
+ link: https://github.com
+
+ - name: twitter
+ icon: FaTwitter
+ link: https://twitter.com
+
+ - name: linkedin
+ icon: FaLinkedin
+ link: https://linkedin.com
+---
+
+lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr navigation et dolore magna aliqua.
diff --git a/src/content/authors/french/sam-wilson.md b/src/content/authors/french/sam-wilson.md
new file mode 100644
index 0000000..d3ab0e5
--- /dev/null
+++ b/src/content/authors/french/sam-wilson.md
@@ -0,0 +1,20 @@
+---
+title: Sam Wilson
+email: samwilson@email.com
+image: "/images/avatar.png"
+description: this is meta description
+social:
+ - name: github
+ icon: FaGithub
+ link: https://github.com
+
+ - name: twitter
+ icon: FaTwitter
+ link: https://twitter.com
+
+ - name: linkedin
+ icon: FaLinkedin
+ link: https://linkedin.com
+---
+
+lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr navigation et dolore magna aliqua.
diff --git a/src/content/authors/french/william-jacob.md b/src/content/authors/french/william-jacob.md
new file mode 100644
index 0000000..193589a
--- /dev/null
+++ b/src/content/authors/french/william-jacob.md
@@ -0,0 +1,20 @@
+---
+title: Guillaume Jacob
+email: williamjacob@email.com
+image: "/images/avatar.png"
+description: this is meta description
+social:
+ - name: github
+ icon: FaGithub
+ link: https://github.com
+
+ - name: twitter
+ icon: FaTwitter
+ link: https://twitter.com
+
+ - name: linkedin
+ icon: FaLinkedin
+ link: https://linkedin.com
+---
+
+lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr navigation et dolore magna aliqua.
diff --git a/src/content/blog/-index.md b/src/content/blog/english/-index.md
similarity index 100%
rename from src/content/blog/-index.md
rename to src/content/blog/english/-index.md
diff --git a/src/content/blog/post-1.md b/src/content/blog/english/post-1.md
similarity index 100%
rename from src/content/blog/post-1.md
rename to src/content/blog/english/post-1.md
diff --git a/src/content/blog/post-2.md b/src/content/blog/english/post-2.md
similarity index 100%
rename from src/content/blog/post-2.md
rename to src/content/blog/english/post-2.md
diff --git a/src/content/blog/post-3.md b/src/content/blog/english/post-3.md
similarity index 100%
rename from src/content/blog/post-3.md
rename to src/content/blog/english/post-3.md
diff --git a/src/content/blog/post-4.md b/src/content/blog/english/post-4.md
similarity index 100%
rename from src/content/blog/post-4.md
rename to src/content/blog/english/post-4.md
diff --git a/src/content/blog/french/-index.md b/src/content/blog/french/-index.md
new file mode 100755
index 0000000..eac3331
--- /dev/null
+++ b/src/content/blog/french/-index.md
@@ -0,0 +1,5 @@
+---
+title: "Articles de blog"
+meta_title: ""
+description: "Ceci est une méta-description"
+---
diff --git a/src/content/blog/french/post-1.md b/src/content/blog/french/post-1.md
new file mode 100755
index 0000000..8d1a370
--- /dev/null
+++ b/src/content/blog/french/post-1.md
@@ -0,0 +1,23 @@
+---
+title: "Comment créer une application avec des technologies modernes"
+meta_title: ""
+description: "Ceci est une méta-description"
+date: 2022-04-04T05:00:00Z
+image: "/images/image-placeholder.png"
+categories: ["french","Application", "Data"]
+author: "John Doe"
+tags: ["nextjs", "tailwind", "react"]
+draft: false
+---
+
+Personne ne veut mĂȘme sortir un maquillage de l'urne des soins empoisonnĂ©s. C'Ă©tait un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+## Design Créatif
+
+Car en guise de maquillage, l'urne du poison C'était un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+> Le client lui-mĂȘme doit pouvoir poursuivre l'adipisicing. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
diff --git a/src/content/blog/french/post-2.md b/src/content/blog/french/post-2.md
new file mode 100755
index 0000000..38a17df
--- /dev/null
+++ b/src/content/blog/french/post-2.md
@@ -0,0 +1,23 @@
+---
+title: "Comment créer une application avec des technologies modernes"
+meta_title: ""
+description: "Ceci est une méta-description"
+date: 2022-04-04T05:00:00Z
+image: "/images/image-placeholder.png"
+categories: ["Technology", "Data"]
+author: "Sam Wilson"
+tags: ["technology", "tailwind"]
+draft: false
+---
+
+Personne ne veut mĂȘme sortir un maquillage de l'urne des soins empoisonnĂ©s. C'Ă©tait un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+## Design Créatif
+
+Car en guise de maquillage, l'urne du poison C'était un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+> Le client lui-mĂȘme doit pouvoir poursuivre l'adipisicing. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
diff --git a/src/content/blog/french/post-3.md b/src/content/blog/french/post-3.md
new file mode 100755
index 0000000..7c1fb81
--- /dev/null
+++ b/src/content/blog/french/post-3.md
@@ -0,0 +1,23 @@
+---
+title: "Comment créer une application avec des technologies modernes"
+meta_title: ""
+description: "Ceci est une méta-description"
+date: 2022-04-04T05:00:00Z
+image: "/images/image-placeholder.png"
+categories: ["Software"]
+author: "John Doe"
+tags: ["software", "tailwind"]
+draft: false
+---
+
+Personne ne veut mĂȘme sortir un maquillage de l'urne des soins empoisonnĂ©s. C'Ă©tait un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+## Design Créatif
+
+Car en guise de maquillage, l'urne du poison C'était un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+> Le client lui-mĂȘme doit pouvoir poursuivre l'adipisicing. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
diff --git a/src/content/blog/french/post-4.md b/src/content/blog/french/post-4.md
new file mode 100755
index 0000000..d489e36
--- /dev/null
+++ b/src/content/blog/french/post-4.md
@@ -0,0 +1,23 @@
+---
+title: "Comment créer une application avec des technologies modernes"
+meta_title: ""
+description: "Ceci est une méta-description"
+date: 2022-04-04T05:00:00Z
+image: "/images/image-placeholder.png"
+categories: ["Architecture"]
+author: "John Doe"
+tags: ["silicon", "technology"]
+draft: false
+---
+
+Personne ne veut mĂȘme sortir un maquillage de l'urne des soins empoisonnĂ©s. C'Ă©tait un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+## Design Créatif
+
+Car en guise de maquillage, l'urne du poison C'était un week-end. Je suis un footballeur complet. Pour boire, le lac occupe le plus grand porche. Chacune des cibles de la vie ne flatte pas Euismod.
+
+> Le client lui-mĂȘme doit pouvoir poursuivre l'adipisicing. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
+
+L'entreprise elle-mĂȘme est une entreprise trĂšs prospĂšre. Personne ne prend mĂȘme la peine de l'ouvrir. Alors je vais ouvrir la naissance pour choisir ? Ătre rejetĂ© par certaines personnes est un choix commode du prĂ©sent pour ressentir une douleur comme la sienne !
diff --git a/src/content/config.ts b/src/content/config.ts
index 6b926a5..c690a41 100755
--- a/src/content/config.ts
+++ b/src/content/config.ts
@@ -1,6 +1,6 @@
import { defineCollection, z } from "astro:content";
-// Post collection schema
+// Blog collection schema
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
@@ -49,9 +49,96 @@ const pagesCollection = defineCollection({
}),
});
+// Contact collection schema
+const contactCollection = defineCollection({
+ schema: z.object({
+ title: z.string(),
+ meta_title: z.string().optional(),
+ description: z.string(),
+ image: z.string().optional(),
+ draft: z.boolean().optional(),
+ }),
+});
+
+// About collection schema
+const aboutCollection = defineCollection({
+ schema: z.object({
+ title: z.string(),
+ meta_title: z.string().optional(),
+ description: z.string().optional(),
+ image: z.string(),
+ draft: z.boolean().optional(),
+ }),
+});
+
+// Banner schema
+const bannerSchema = z.object({
+ title: z.string(),
+ content: z.string(),
+ image: z.string(),
+ button: z.object({
+ enable: z.boolean(),
+ label: z.string(),
+ link: z.string(),
+ }),
+});
+
+// Features schema
+const featureSchema = z.object({
+ title: z.string(),
+ image: z.string(),
+ content: z.string(),
+ bulletpoints: z.array(z.string()),
+ button: z.object({
+ enable: z.boolean(),
+ label: z.string().optional(),
+ link: z.string().optional(),
+ }),
+});
+
+// Content schema (for the main content structure with banner and features)
+const contentSchema = z.object({
+ banner: bannerSchema,
+ features: z.array(featureSchema),
+});
+
+// Content collection schema
+const contentCollection = defineCollection({
+ schema: contentSchema,
+});
+
+// Testimonial schema
+const testimonialSchema = z.object({
+ name: z.string(),
+ designation: z.string(),
+ avatar: z.string(),
+ content: z.string(),
+});
+
+// Testimonials schema
+const testimonialsSchema = z.array(testimonialSchema);
+
+// Call to Action schema
+const callToActionSchema = z.object({
+ enable: z.boolean(),
+ title: z.string(),
+ image: z.string(),
+ description: z.string(),
+ button: z.object({
+ enable: z.boolean(),
+ label: z.string(),
+ link: z.string().url(),
+ }),
+});
+
// Export collections
export const collections = {
blog: blogCollection,
authors: authorsCollection,
pages: pagesCollection,
+ contact: contactCollection,
+ about: aboutCollection,
+ content: contentCollection,
+ testimonials: testimonialsSchema,
+ callToAction: callToActionSchema,
};
diff --git a/src/content/contact/-index.md b/src/content/contact/english/-index.md
similarity index 100%
rename from src/content/contact/-index.md
rename to src/content/contact/english/-index.md
diff --git a/src/content/contact/french/-index.md b/src/content/contact/french/-index.md
new file mode 100644
index 0000000..bd6b9f6
--- /dev/null
+++ b/src/content/contact/french/-index.md
@@ -0,0 +1,6 @@
+---
+title: "Contact"
+meta_title: ""
+description: "this is meta description"
+draft: false
+---
diff --git a/src/content/homepage/-index.md b/src/content/homepage/english/-index.md
similarity index 100%
rename from src/content/homepage/-index.md
rename to src/content/homepage/english/-index.md
diff --git a/src/content/homepage/french/-index.md b/src/content/homepage/french/-index.md
new file mode 100755
index 0000000..be96b3f
--- /dev/null
+++ b/src/content/homepage/french/-index.md
@@ -0,0 +1,53 @@
+---
+# Banner
+banner:
+ title: "Le modÚle de démarrage ultime dont vous avez besoin pour démarrer votre projet Astro"
+ content: "Astroplate est un modÚle de démarrage gratuit construit avec Astro et TailwindCSS, fournissant tout ce dont vous avez besoin pour démarrer votre projet Astro et gagner un temps précieux."
+ image: "/images/banner.png"
+ button:
+ enable: true
+ label: "Commencer gratuitement"
+ link: "https://github.com/zeon-studio/astroplate"
+
+# Features
+features:
+ - title: "Ce qui est inclus dans Astroplate"
+ image: "/images/service-1.png"
+ content: "Astroplate est un modÚle de démarrage complet qui inclut tout ce dont vous avez besoin pour démarrer votre projet Astro. Ce qui est inclus dans Astroplate"
+ bulletpoints:
+ - "10+ pages pré-construites"
+ - "Score Google Pagespeed de 95+"
+ - "Construit avec Astro et TailwindCSS pour un style facile et personnalisable"
+ - "EntiĂšrement responsive sur tous les appareils"
+ - "Optimisé pour le référencement pour de meilleurs classements dans les moteurs de recherche"
+ - "**Open-source et gratuit** pour une utilisation personnelle et commerciale"
+ button:
+ enable: false
+ label: "Commencer maintenant"
+ link: "#"
+
+ - title: "Découvrez les fonctionnalités clés d'Astro"
+ image: "/images/service-2.png"
+ content: "Astro est un framework web tout-en-un pour construire des sites web rapides et centrés sur le contenu. Il offre une gamme de fonctionnalités excitantes pour les développeurs et les créateurs de sites web. Certaines des fonctionnalités clés sont :"
+ bulletpoints:
+ - "Zéro JS, par défaut : Aucun surcoût d'exécution JavaScript pour vous ralentir."
+ - "Personnalisable : Tailwind, MDX et plus de 100 autres intégrations au choix."
+ - "Agnostique Ă l'UI : Supporte React, Preact, Svelte, Vue, Solid, Lit et plus encore."
+ button:
+ enable: true
+ label: "Commencer maintenant"
+ link: "https://github.com/zeon-studio/astroplate"
+
+ - title: "Les principales raisons de choisir Astro pour votre prochain projet"
+ image: "/images/service-3.png"
+ content: "Avec Astro, vous pouvez construire des sites web modernes et centrés sur le contenu sans sacrifier la performance ou la facilité d'utilisation."
+ bulletpoints:
+ - "Charge instantanée des sites statiques pour une meilleure expérience utilisateur et SEO."
+ - "Syntaxe intuitive et support pour les frameworks populaires rendent l'apprentissage et l'utilisation d'Astro un jeu d'enfant."
+ - "Utilisez n'importe quelle bibliothÚque ou framework front-end, ou construisez des composants personnalisés, pour tout type de projet."
+ - "Construit sur une technologie de pointe pour garder vos projets Ă jour avec les derniĂšres normes web."
+ button:
+ enable: false
+ label: ""
+ link: ""
+---
diff --git a/src/content/pages/elements.mdx b/src/content/pages/english/elements.mdx
similarity index 100%
rename from src/content/pages/elements.mdx
rename to src/content/pages/english/elements.mdx
diff --git a/src/content/pages/privacy-policy.md b/src/content/pages/english/privacy-policy.md
similarity index 100%
rename from src/content/pages/privacy-policy.md
rename to src/content/pages/english/privacy-policy.md
diff --git a/src/content/pages/french/elements.mdx b/src/content/pages/french/elements.mdx
new file mode 100755
index 0000000..c0301a3
--- /dev/null
+++ b/src/content/pages/french/elements.mdx
@@ -0,0 +1,255 @@
+---
+title: "ĂlĂ©ments"
+meta_title: ""
+description: "Ceci est une méta-description"
+draft: false
+---
+
+# Titre 1
+
+## Titre 2
+
+### Titre 3
+
+#### Titre 4
+
+##### Titre 5
+
+###### Titre 6
+
+---
+
+### Paragraphe
+
+Ătes-vous venu ici pour quelque chose en particulier ou juste pour critiquer Riker? Et en entrant Ă vitesse maximale, vous ĂȘtes apparu pour un instant ĂȘtre Ă deux endroits Ă la fois. Nous avons un saboteur Ă bord. Nous savons que vous vous occupez de minerai volĂ©. Mais je veux parler de la tentative d'assassinat sur le Lieutenant Worf. Quelqu'un pourrait-il survivre dans un tampon de tĂ©lĂ©porteur pendant 75 ans? Le destin. Il protĂšge les fous, les petits enfants et les navires.
+
+Ătes-vous venu ici pour quelque chose en particulier ou juste pour critiquer Riker? Et en entrant Ă vitesse maximale, vous ĂȘtes apparu pour un instant ĂȘtre Ă deux endroits Ă la fois. Nous avons un saboteur Ă bord. Nous savons que vous vous occupez de minerai volĂ©. Mais je veux parler de la tentative d'assassinat sur le Lieutenant Worf. Quelqu'un pourrait-il survivre dans un tampon de tĂ©lĂ©porteur pendant 75 ans? Le destin. Il protĂšge les fous, les petits enfants et les navires.
+
+---
+
+### Emphase
+
+1. Ătes-vous venu ici pour quelque chose en **particulier** ou juste en gĂ©nĂ©ral
+
+2. Ătes-vous venu ici pour quelque chose en particulier
+
+3. _Ătes-vous venu ici_
+
+4. Ătes-vous venu ici pour **quelque chose** en particulier
+
+5. Ătes-vous venu ici pour quelque chose en particulier
+
+6. Ătes-vous venu ici pour quelque chose en particulier
+
+7. Les URLs et les URLs entre crochets seront automatiquement transformées en liens. [http://www.example.com](http://www.example.com) ou
+
+8. [http://www.example.com](http://www.example.com) et parfois example.com (mais pas sur Github, par exemple).
+
+---
+
+### Lien
+
+[Je suis un lien en ligne](https://www.google.com)
+
+[Je suis un lien en ligne avec titre](https://www.google.com "Page d'accueil de Google")
+
+[Je suis un lien de référence][texte de référence insensible à la casse]
+
+[Je suis une référence relative à un fichier de dépÎt](../blob/master/LICENSE)
+
+[Vous pouvez utiliser des chiffres pour les définitions de liens de référence][1]
+
+Ou laissez-le vide et utilisez [le texte du lien lui-mĂȘme].
+
+example.com (mais pas sur Github, par exemple).
+
+Du texte pour montrer que les liens de référence peuvent suivre plus tard.
+
+[texte de référence insensible à la casse]: https://www.themefisher.com
+[1]: https://gethugothemes.com
+[le texte du lien lui-mĂȘme]: https://www.getjekyllthemes.com
+
+---
+
+### Liste Ordonnée
+
+1. ĂlĂ©ment de liste
+2. ĂlĂ©ment de liste
+3. ĂlĂ©ment de liste
+4. ĂlĂ©ment de liste
+5. ĂlĂ©ment de liste
+
+---
+
+### Liste Non Ordonnée
+
+- ĂlĂ©ment de liste
+- ĂlĂ©ment de liste
+- ĂlĂ©ment de liste
+- ĂlĂ©ment de liste
+- ĂlĂ©ment de liste
+
+---
+
+### Code and Syntax Highlighting
+
+#### HTML
+
+```html
+
+```
+
+---
+
+#### CSS
+
+```css
+img {
+ vertical-align: middle;
+ border: 0;
+ max-width: 100%;
+ height: auto;
+}
+```
+
+---
+
+#### JavaScript
+
+```javascript
+window.addEventListener("load", (e) => {
+ document.querySelector(".preloader").style.display = "none";
+});
+```
+
+---
+
+### Button
+
+
+
+---
+
+### Quote
+
+> Ătes-vous venu ici pour quelque chose en particulier ou simplement pour dĂ©nigrer Riker en gĂ©nĂ©ral ? Et en soufflant Ă la vitesse de distorsion maximale, vous avez semblĂ© pendant un instant ĂȘtre Ă deux endroits Ă la fois.
+
+---
+
+### Notice
+
+Ceci est une simple remarque.
+
+Ceci est une simple remarque.
+
+Ceci est une simple remarque.
+
+Ceci est une simple remarque.
+
+---
+
+### Tab
+
+
+
+
+
+#### Did you come here for something in particular?
+
+Ătes-vous venu ici pour quelque chose en particulier ou simplement pour dĂ©nigrer Riker en gĂ©nĂ©ral ? Et en soufflant Ă la vitesse de distorsion maximale, vous avez semblĂ© pendant un instant ĂȘtre Ă deux endroits Ă la fois. Nous avons un saboteur Ă bord. Nous savons que vous faites du trafic de minerai volĂ©. Mais je veux parler de la tentative d'assassinat du lieutenant Worf.
+
+
+
+
+
+#### I wanna talk about the assassination attempt
+
+La douleur elle-mĂȘme est grande, l'Ă©litr sadipscing est rĂ©glĂ©, mais les diam nonumy eirmo pendant un temps ils envient qu'avec le travail et la douleur c'Ă©tait quelque chose de grand, mais le diam voluptiva. Mais en effet, je les accuserai et seulement deux douleurs et je les reprendrai. Stet clita kasd gubergren, aucun takimata de mer n'est sacrĂ©.
+
+La douleur elle-mĂȘme est grande, l'Ă©litr sadipscing est rĂ©glĂ©, mais les diam nonumy eirmo pendant un temps ils envient qu'avec le travail et la douleur c'Ă©tait quelque chose de grand, mais le diam voluptiva. Mais en effet, je les accuserai et seulement deux douleurs et je les reprendrai. Stet clita kasd gubergren, aucun takimata de mer n'est sacrĂ©.
+
+
+
+
+
+#### We know youâre dealing in stolen ore
+
+La douleur elle-mĂȘme est grande, l'Ă©litr sadipscing est rĂ©glĂ©, mais les diam nonumy eirmo pendant un temps ils envient qu'avec le travail et la douleur c'Ă©tait quelque chose de grand, mais le diam voluptiva. Mais en effet, je les accuserai et seulement deux douleurs et je les reprendrai. Stet clita kasd gubergren, aucun takimata de mer n'est sacrĂ©.
+
+La douleur elle-mĂȘme est grande, l'Ă©litr sadipscing est rĂ©glĂ©, mais les diam nonumy eirmo pendant un temps ils envient qu'avec le travail et la douleur c'Ă©tait quelque chose de grand, mais le diam voluptiva. Mais en effet, je les accuserai et seulement deux douleurs et je les reprendrai. Stet clita kasd gubergren, aucun takimata de mer n'est sacrĂ©.
+
+
+
+
+
+---
+
+### Table
+
+| # | First | Last | Handle |
+| :-- | :----------: | :----------: | -----------: |
+| 1 | Row:1 Cell:1 | Row:1 Cell:2 | Row:1 Cell:3 |
+| 2 | Row:2 Cell:1 | Row:2 Cell:2 | Row:2 Cell:3 |
+| 3 | Row:3 Cell:1 | Row:3 Cell:2 | Row:3 Cell:3 |
+
+---
+
+### Accordion
+
+
+
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+
+
+
+
+
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+
+
+
+
+
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+- C'est une chose.
+
+
+
+---
+
+### Image
+
+
+
+---
+
+### Youtube video
+
+
+
+---
+
+### Custom video
+
+
diff --git a/src/content/pages/french/privacy-policy.md b/src/content/pages/french/privacy-policy.md
new file mode 100644
index 0000000..8077b19
--- /dev/null
+++ b/src/content/pages/french/privacy-policy.md
@@ -0,0 +1,30 @@
+---
+title: "Confidentialité"
+meta_title: ""
+description: "this is meta description"
+draft: false
+---
+
+#### Responsabilité des contributeurs
+
+Le client est trĂšs important, le client sera suivi par le client. Purus, jusqu'Ă prĂ©sent tu es eros, ullamcorper id feugiat chaque personne avec quelques flĂšches. J'Ă©tais laide, mais la masse des dessins animĂ©s Ă©tait grosse d'un frĂ©missement. Je ne veux pas de la douleur dâun footballeur puissant. Ultrices amoat, dans sera traitĂ© de l'arc du lit de la maladie. Il a Ă©tĂ© touchĂ© par des flĂšches lors d'un cours de torture. Le problĂšme avec la fondue, c'est qu'il avait besoin de chocolat. Maintenant le bureau mais parfois le lac. Mais qui se soucie des dessins animĂ©s
+
+le prix peut ĂȘtre certain. C'est un Ă©lĂ©ment trĂšs important, tincidunt eros, nibh en lion. Malesuada est un lac pur, mais suspendu pendant un certain temps. Qui se soucie du temps, veut naĂźtre. Mais il faut en prendre soin. Eu veut naĂźtre du souci erhdfvssfvrgss a besoin de dessins animĂ©s et d'Ă©lĂ©ments. Un lac dans lequel il est facile de se baigner.
+
+#### Collecte d'informations personnelles
+
+Le client est trĂšs important, le client sera suivi par le client. Purus, jusqu'Ă prĂ©sent tu es eros, ullamcorper id feugiat chaque personne avec quelques flĂšches. J'Ă©tais laide, mais la masse des dessins animĂ©s Ă©tait grosse d'un frĂ©missement. Ne me donne pas la douleur d'un joueur de football puissant. Les ultrices vont ĂȘtre guĂ©ries par l'arc du lit de la maladie. Il a Ă©tĂ© touchĂ© par des flĂšches lors d'un cours de torture. Le problĂšme avec la fondue, c'est qu'il avait besoin de chocolat. Maintenant le bureau mais parfois le lac. Mais qui se soucie des dessins animĂ©s ?
+
+#### Protection des informations personnelles
+
+Le client est trĂšs important, le client sera suivi par le client. Purus, jusqu'Ă prĂ©sent tu es eros, ullamcorper id feugiat chaque personne avec quelques flĂšches. J'Ă©tais laide, mais la masse des dessins animĂ©s Ă©tait grosse d'un frĂ©missement. Je ne veux pas de la douleur dâun footballeur puissant. Les ultrices vont ĂȘtre guĂ©ries par l'arc du lit de la maladie. Il a Ă©tĂ© touchĂ© par des flĂšches lors d'un cours de macro.
+
+Le problÚme avec la marmite, c'était qu'il avait besoin de chocolat. Maintenant le bureau mais parfois le lac. Mais qui se soucie des dessins animés ?
+Le client est trĂšs important, le client sera suivi par le client. Purus, jusqu'Ă maintenant tu es eros, ullamcorper id feugiat
+
+#### Modifications de la politique de confidentialité
+
+1. Tous les articles Themefisher sont conçus pour ĂȘtre les plus rĂ©cents, nous vĂ©rifions tous
+2. commentaires qui menacent ou nuisent à la réputation de toute personne ou organisation
+3. informations personnelles, y compris, sans toutefois s'y limiter, les adresses e-mail et les numéros de téléphone
+4. Toute mise Ă jour arrive. Le client technologique recevra une notification automatique.
\ No newline at end of file
diff --git a/src/content/sections/call-to-action.md b/src/content/sections/english/call-to-action.md
similarity index 100%
rename from src/content/sections/call-to-action.md
rename to src/content/sections/english/call-to-action.md
diff --git a/src/content/sections/testimonial.md b/src/content/sections/english/testimonial.md
similarity index 100%
rename from src/content/sections/testimonial.md
rename to src/content/sections/english/testimonial.md
diff --git a/src/content/sections/french/call-to-action.md b/src/content/sections/french/call-to-action.md
new file mode 100644
index 0000000..c7a241b
--- /dev/null
+++ b/src/content/sections/french/call-to-action.md
@@ -0,0 +1,10 @@
+---
+enable: true
+title: "PrĂȘt Ă construire votre prochain projet avec Astro ?"
+image: "/images/call-to-action.png"
+description: "Expérimente le futur du développement web avec Astroplate et Astro. Créez des sites statiques à charge rapide et personnalisables en toute facilité."
+button:
+ enable: true
+ label: "Commencez maintenant"
+ link: "https://github.com/zeon-studio/astroplate"
+---
diff --git a/src/content/sections/french/testimonial.md b/src/content/sections/french/testimonial.md
new file mode 100644
index 0000000..0f62df6
--- /dev/null
+++ b/src/content/sections/french/testimonial.md
@@ -0,0 +1,27 @@
+---
+enable: true
+title: "Ce que disent les utilisateurs d'Astroplate"
+description: "Ne vous contentez pas de nous croire sur parole - écoutez certains de nos utilisateurs satisfaits ! Consultez quelques-uns de nos témoignages ci-dessous pour voir ce que les autres disent à propos d'Astroplate."
+
+# Testimonials
+testimonials:
+ - name: "Marvin McKinney"
+ designation: "Concepteur Web"
+ avatar: "/images/avatar-sm.png"
+ content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
+
+ - name: "Marvin McKinney"
+ designation: "Concepteur Web"
+ avatar: "/images/avatar-sm.png"
+ content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
+
+ - name: "Marvin McKinney"
+ designation: "Concepteur Web"
+ avatar: "/images/avatar-sm.png"
+ content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
+
+ - name: "Marvin McKinney"
+ designation: "Concepteur Web"
+ avatar: "/images/avatar-sm.png"
+ content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
+---
diff --git a/src/i18n/en.json b/src/i18n/en.json
new file mode 100644
index 0000000..64f04bf
--- /dev/null
+++ b/src/i18n/en.json
@@ -0,0 +1,16 @@
+{
+ "full_name": "Full Name",
+ "full_name_placeholder": "Enter your full name",
+ "mail_placeholder": "john.doe@email.com",
+ "message": "Anything else?",
+ "message_placeholder": "Your message here...",
+ "submit": "Submit",
+ "read_more": "Read More",
+ "page_not_found": "Page Not Found",
+ "working_mail": "Work Email",
+ "anything_else": "Anything else?",
+ "contact_message_placeholder": "Your message here...",
+ "page_not_found_content": "The page you are looking for may have been removed, renamed, or is temporarily unavailable.",
+ "back_to_home": "Back to Home",
+ "get_started": "Get Started"
+}
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
new file mode 100644
index 0000000..62f4e06
--- /dev/null
+++ b/src/i18n/fr.json
@@ -0,0 +1,16 @@
+{
+ "full_name": "Nom complet",
+ "full_name_placeholder": "Entrez votre nom complet",
+ "mail_placeholder": "john.doe@email.com",
+ "message": "Autre chose?",
+ "message_placeholder": "Votre message ici...",
+ "submit": "Soumettre",
+ "read_more": "Lire la suite",
+ "page_not_found": "Page non trouvée",
+ "working_mail": "Courrier de travail",
+ "anything_else": "Autre chose ?",
+ "contact_message_placeholder": "Votre message ici...",
+ "page_not_found_content": "La page que vous recherchez a peut-ĂȘtre Ă©tĂ© supprimĂ©e, changĂ©e de nom ou temporairement inaccessible.",
+ "back_to_home": "Retour Ă l'accueil",
+ "get_started": "Commencez Ă utiliser"
+}
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
index 5c189eb..e539626 100755
--- a/src/layouts/Base.astro
+++ b/src/layouts/Base.astro
@@ -9,6 +9,7 @@ import "@/styles/main.scss";
import { AstroFont } from "astro-font";
import { ViewTransitions } from "astro:transitions";
import SearchModal from "./helpers/SearchModal";
+import { getLangFromUrl } from "@/lib/utils/languageParser";
// font families
const pf = theme.fonts.font_family.primary;
@@ -18,12 +19,12 @@ let fontPrimary, fontSecondary;
if (theme.fonts.font_family.primary) {
fontPrimary = theme.fonts.font_family.primary
.replace(/\+/g, " ")
- .replace(/:[ital,]*[ital@]*[wght@]*[0-9,;.]+/gi, "");
+ .replace(/:[ital,]*[ital@]*[wght@]*[0-9,;]+/gi, "");
}
if (theme.fonts.font_family.secondary) {
fontSecondary = theme.fonts.font_family.secondary
.replace(/\+/g, " ")
- .replace(/:[ital,]*[ital@]*[wght@]*[0-9,;.]+/gi, "");
+ .replace(/:[ital,]*[ital@]*[wght@]*[0-9,;]+/gi, "");
}
// types for frontmatters
@@ -34,15 +35,18 @@ export interface Props {
image?: string;
noindex?: boolean;
canonical?: string;
+ lang?: string;
}
-// destructure frontmatter
-const { title, meta_title, description, image, noindex, canonical } =
+// distructure frontmatters
+const { title, meta_title, description, image, noindex, canonical, lang } =
Astro.props;
+
+const language = lang || getLangFromUrl(Astro.url);
---
-
+
@@ -173,7 +177,7 @@ const { title, meta_title, description, image, noindex, canonical } =
-
+
diff --git a/src/layouts/PostSingle.astro b/src/layouts/PostSingle.astro
index 5f59a02..66d66bf 100755
--- a/src/layouts/PostSingle.astro
+++ b/src/layouts/PostSingle.astro
@@ -1,18 +1,30 @@
---
import BlogCard from "@/components/BlogCard.astro";
import Share from "@/components/Share.astro";
+import config from "@/config/config.json";
import Disqus from "@/helpers/Disqus";
import { getSinglePage } from "@/lib/contentParser.astro";
import dateFormat from "@/lib/utils/dateFormat";
+import { slugSelector } from "@/lib/utils/languageParser";
import similarItems from "@/lib/utils/similarItems";
import { humanize, markdownify, slugify } from "@/lib/utils/textConverter";
+import type { ContentEntryMap } from "astro:content";
import { FaRegClock, FaRegFolder, FaRegUserCircle } from "react-icons/fa";
import ImageMod from "./components/ImageMod.astro";
+const { default_language } = config.settings;
const COLLECTION_FOLDER = "blog";
const { post } = Astro.props;
+let { lang } = Astro.params;
-const posts = await getSinglePage(COLLECTION_FOLDER);
+if (!lang) {
+ lang = default_language;
+}
+
+const posts = await getSinglePage(
+ COLLECTION_FOLDER,
+ lang as keyof ContentEntryMap
+);
const similarPosts = similarItems(post, posts);
const { Content } = await post.render();
const { title, description, author, categories, image, date, tags } = post.data;
@@ -39,7 +51,7 @@ const { title, description, author, categories, image, date, tags } = post.data;
-
-
+
{humanize(author)}
@@ -48,7 +60,9 @@ const { title, description, author, categories, image, date, tags } = post.data;
{
categories.map((category: string, index: number) => (
-
+
{humanize(category)}
{index !== categories.length - 1 && ","}
@@ -72,7 +86,7 @@ const { title, description, author, categories, image, date, tags } = post.data;
-
{humanize(tag)}
diff --git a/src/layouts/components/AuthorCard.astro b/src/layouts/components/AuthorCard.astro
index bd00f86..374acf9 100644
--- a/src/layouts/components/AuthorCard.astro
+++ b/src/layouts/components/AuthorCard.astro
@@ -1,10 +1,12 @@
---
+import { getLangFromUrl, slugSelector } from "@/lib/utils/languageParser";
import { plainify } from "@/lib/utils/textConverter";
import ImageMod from "./ImageMod.astro";
import Social from "./Social.astro";
const { data } = Astro.props;
const { title, image, social } = data.data;
+const lang = getLangFromUrl(Astro.url);
---
-
{title}
+
{title}
{plainify(data.body?.slice(0, 100))}
diff --git a/src/layouts/components/BlogCard.astro b/src/layouts/components/BlogCard.astro
index 268a532..eb15361 100644
--- a/src/layouts/components/BlogCard.astro
+++ b/src/layouts/components/BlogCard.astro
@@ -1,16 +1,28 @@
---
import config from "@/config/config.json";
import dateFormat from "@/lib/utils/dateFormat";
+import {
+ getLangFromUrl,
+ getTranslations,
+ slugSelector,
+} from "@/lib/utils/languageParser";
import { humanize, plainify, slugify } from "@/lib/utils/textConverter";
+import type { ContentEntryMap } from "astro:content";
import { FaRegFolder, FaRegUserCircle } from "react-icons/fa";
import ImageMod from "./ImageMod.astro";
-const {
- summary_length,
- blog_folder,
-}: { summary_length: number; blog_folder: string } = config.settings;
+const { summary_length }: { summary_length: number; blog_folder: string } =
+ config.settings;
const { data } = Astro.props;
const { title, image, date, author, categories } = data.data;
+
+const lang = getLangFromUrl(Astro.url);
+const { read_more } = await getTranslations(lang as keyof ContentEntryMap);
+
+const slugParts = data.slug.split("/");
+slugParts[0] = "blog";
+const modifiedSlug = slugParts.join("/");
+data.slug = modifiedSlug;
---
@@ -27,22 +39,23 @@ const { title, image, date, author, categories } = data.data;
)
}
-
+
{title}
diff --git a/src/layouts/components/Breadcrumbs.astro b/src/layouts/components/Breadcrumbs.astro
index ed67ed1..29e0448 100644
--- a/src/layouts/components/Breadcrumbs.astro
+++ b/src/layouts/components/Breadcrumbs.astro
@@ -1,19 +1,30 @@
---
+import { supportedLang } from "@/lib/utils/languageParser";
import { humanize } from "@/lib/utils/textConverter";
const { className }: { className?: string } = Astro.props;
const paths = Astro.url.pathname.split("/").filter((x) => x);
+
+let lang = "";
+if (supportedLang.includes(paths[0])) {
+ lang = paths.shift()!;
+}
+
+let baseHref = lang ? `/${lang}` : "/";
let parts = [
{
label: "Home",
- href: "/",
- "aria-label": Astro.url.pathname === "/" ? "page" : undefined,
+ href: baseHref,
+ "aria-label":
+ Astro.url.pathname === baseHref || Astro.url.pathname === `${baseHref}/`
+ ? "page"
+ : undefined,
},
];
paths.forEach((label: string, i: number) => {
- const href = `/${paths.slice(0, i + 1).join("/")}`;
+ const href = `${baseHref}${paths.slice(0, i + 1).join("/")}`;
label !== "page" &&
parts.push({
label: humanize(label.replace(".html", "").replace(/[-_]/g, " ")) || "",
@@ -28,7 +39,7 @@ paths.forEach((label: string, i: number) => {
{
parts.map(({ label, ...attrs }, index) => (
-
- {index > 0 && /}
+ {index > 0 && /}
{index !== parts.length - 1 ? (
{label}
diff --git a/src/layouts/components/Logo.astro b/src/layouts/components/Logo.astro
index 8b7ba5c..730d0c7 100644
--- a/src/layouts/components/Logo.astro
+++ b/src/layouts/components/Logo.astro
@@ -1,6 +1,7 @@
---
import config from "@/config/config.json";
import ImageMod from "./ImageMod.astro";
+import { getLangFromUrl, slugSelector } from "@/lib/utils/languageParser";
const { src, srcDarkmode }: { src?: string; srcDarkmode?: string } =
Astro.props;
@@ -21,9 +22,17 @@ const {
} = config.site;
const { theme_switcher }: { theme_switcher: boolean } = config.settings;
+const { default_language } = config.settings;
+
+let lang = getLangFromUrl(Astro.url);
+
+const disabledLanguages = config.settings.disable_languages as string[];
+if (disabledLanguages.includes(lang)) {
+ lang = default_language;
+}
---
-
+
{
src || srcDarkmode || logo || logo_darkmode ? (
<>
diff --git a/src/layouts/components/Pagination.astro b/src/layouts/components/Pagination.astro
index 3e42979..8bf80f8 100644
--- a/src/layouts/components/Pagination.astro
+++ b/src/layouts/components/Pagination.astro
@@ -1,4 +1,8 @@
---
+import { getLangFromUrl, slugSelector } from "@/lib/utils/languageParser";
+
+const lang = getLangFromUrl(Astro.url);
+
type Pagination = {
section?: string;
currentPage?: number;
@@ -27,8 +31,11 @@ for (let i = 1; i <= totalPages; i++) {
@@ -77,11 +84,12 @@ for (let i = 1; i <= totalPages; i++) {
) : (
@@ -93,7 +101,10 @@ for (let i = 1; i <= totalPages; i++) {
{/* next page */}
{hasNextPage ? (
Next
diff --git a/src/layouts/helpers/LanguageSwitcher.tsx b/src/layouts/helpers/LanguageSwitcher.tsx
new file mode 100644
index 0000000..e268ab9
--- /dev/null
+++ b/src/layouts/helpers/LanguageSwitcher.tsx
@@ -0,0 +1,65 @@
+import config from "@/config/config.json";
+import languages from "@/config/language.json";
+import React from "react";
+
+const LanguageSwitcher = ({
+ lang,
+ pathname,
+}: {
+ lang: string;
+ pathname: string;
+}) => {
+ const { default_language, default_language_in_subdir } = config.settings;
+
+ // Function to remove trailing slash if necessary
+ const removeTrailingSlash = (path: string) => {
+ if (!config.site.trailing_slash) {
+ return path.replace(/\/$/, "");
+ }
+ return path;
+ };
+
+ // Sort languages by weight and filter out disabled languages
+ const sortedLanguages = languages
+ // @ts-ignore
+ .filter(language => !config.settings.disable_languages.includes(language.languageCode))
+ .sort((a, b) => a.weight - b.weight);
+
+ return (
+
1 ? "block" : "hidden"}`}>
+
+
+ );
+};
+
+export default LanguageSwitcher;
diff --git a/src/layouts/helpers/SearchModal.tsx b/src/layouts/helpers/SearchModal.tsx
index 6d29287..f095d7f 100644
--- a/src/layouts/helpers/SearchModal.tsx
+++ b/src/layouts/helpers/SearchModal.tsx
@@ -1,8 +1,11 @@
import searchData from ".json/search.json";
import React, { useEffect, useState } from "react";
import SearchResult, { type ISearchItem } from "./SearchResult";
+import config from "@/config/config.json";
+const { default_language } = config.settings;
-const SearchModal = () => {
+const SearchModal = ({ lang }: { lang: string | undefined }) => {
+ lang = lang || default_language;
const [searchString, setSearchString] = useState("");
// handle input change
@@ -39,9 +42,13 @@ const SearchModal = () => {
}
};
+ // filter language specific search data
+ const filterSearchData = searchData.filter((item) => item.lang === lang);
+
+
// get search result
const startTime = performance.now();
- const searchResult = doSearch(searchData);
+ const searchResult = doSearch(filterSearchData);
const endTime = performance.now();
const totalTime = ((endTime - startTime) / 1000).toFixed(3);
@@ -169,10 +176,15 @@ const SearchModal = () => {
name="search"
value={searchString}
onChange={handleSearch}
+ autoFocus
autoComplete="off"
/>
-
+
diff --git a/src/layouts/helpers/SearchResult.tsx b/src/layouts/helpers/SearchResult.tsx
index 6c0efae..810cc3c 100755
--- a/src/layouts/helpers/SearchResult.tsx
+++ b/src/layouts/helpers/SearchResult.tsx
@@ -1,7 +1,9 @@
+import { slugSelector } from "@/lib/utils/languageParser";
import { plainify, titleify } from "@/lib/utils/textConverter";
import React from "react";
export interface ISearchItem {
+ lang: string;
group: string;
slug: string;
frontmatter: {
@@ -33,10 +35,13 @@ export interface ISearchGroup {
const SearchResult = ({
searchResult,
searchString,
+ lang
}: {
searchResult: ISearchItem[];
searchString: string;
+ lang: string;
}) => {
+
// generate search result group
const generateSearchGroup = (searchResult: ISearchItem[]) => {
const joinDataByGroup: ISearchGroup[] = searchResult.reduce(
@@ -83,6 +88,7 @@ const SearchResult = ({
);
};
+
// match underline
const matchUnderline = (text: string, substring: string) => {
const parts = text?.split(new RegExp(`(${substring})`, "gi"));
@@ -148,12 +154,14 @@ const SearchResult = ({
)}
{matchUnderline(item.frontmatter.title, searchString)}
@@ -188,8 +196,8 @@ const SearchResult = ({
{matchUnderline(category, searchString)}
{item.frontmatter.categories &&
index !==
- item.frontmatter.categories.length -
- 1 && <>, >}
+ item.frontmatter.categories.length -
+ 1 && <>, >}
),
)}
@@ -211,7 +219,7 @@ const SearchResult = ({
{matchUnderline(tag, searchString)}
{item.frontmatter.tags &&
index !==
- item.frontmatter.tags.length - 1 && <>, >}
+ item.frontmatter.tags.length - 1 && <>, >}
))}
diff --git a/src/layouts/partials/Footer.astro b/src/layouts/partials/Footer.astro
index cd1b641..09d6912 100755
--- a/src/layouts/partials/Footer.astro
+++ b/src/layouts/partials/Footer.astro
@@ -2,11 +2,21 @@
import Logo from "@/components/Logo.astro";
import Social from "@/components/Social.astro";
import config from "@/config/config.json";
-import menu from "@/config/menu.json";
import social from "@/config/social.json";
+import {
+ getLangFromUrl,
+ getTranslations,
+ slugSelector,
+} from "@/lib/utils/languageParser";
import { markdownify } from "@/lib/utils/textConverter";
+import type { ContentEntryMap } from "astro:content";
-const { footer }: { footer: { name: string; url: string }[] } = menu;
+const lang = getLangFromUrl(Astro.url);
+const menu = await getTranslations(lang as keyof ContentEntryMap);
+let footer: any = [];
+if (menu) {
+ footer = menu.footer;
+}
---