diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..0f7c283
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,50 @@
+{
+ "name": "Debian",
+ "image": "mcr.microsoft.com/devcontainers/base:bullseye",
+ "features": {
+ "ghcr.io/devcontainers/features/node:1": {},
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {},
+ "ghcr.io/devcontainers/features/github-cli:1": {}
+ },
+ "forwardPorts": [
+ 4321
+ ],
+ "secrets": {},
+ "remoteEnv": {
+ "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}"
+ },
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "eamodio.gitlens",
+ "github.copilot",
+ "github.copilot-chat",
+ "github.vscode-github-actions",
+ "ms-vscode.makefile-tools",
+ "astro-build.astro-vscode",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode",
+ "bradlc.vscode-tailwindcss"
+ ],
+ "settings": {
+ "terminal.integrated.defaultProfile.linux": "zsh"
+ },
+ "mcp": {
+ "servers": {
+ "playwright": {
+ "command": "npx",
+ "args": [
+ "@playwright/mcp@latest"
+ ]
+ },
+ "lighthouse": {
+ "command": "npx",
+ "args": [
+ "lighthouse-mcp"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c7f525f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+FROM ghcr.io/super-linter/super-linter:slim-v8.0.0
+
+HEALTHCHECK --interval=5m --timeout=10s --start-period=30s --retries=3 CMD ["/bin/sh","-c","test -d /github/home"]
+ARG UID=1000
+ARG GID=1000
+RUN chown -R ${UID}:${GID} /github/home
+USER ${UID}:${GID}
+
+ENV RUN_LOCAL=true
+ENV USE_FIND_ALGORITHM=false
+ENV IGNORE_GITIGNORED_FILES=true
+ENV LOG_LEVEL=WARN
+ENV LOG_FILE="/github/home/logs"
+ENV DEFAULT_BRANCH=main
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..986f1c5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,88 @@
+.PHONY: help
+
+MAKEFLAGS += --silent
+.DEFAULT_GOAL := help
+
+help: ## Show help message
+ @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
+
+include .env
+
+setup: ## Prepare stack to run
+ cd application && npm install
+ cd .github/actions/generate-blog-post && npm install
+ cd .github/actions/generate-brand-content && npm install
+ cd .github/actions/validate-manifest && npm install
+
+start: ## Start application in dev mode
+ cd application && npm run start
+
+lint: ## Run linters
+ cd application && npm run lint -- $(filter-out $@,$(MAKECMDGOALS))
+ $(call run_linter,)
+
+lint-fix: ## Run linters
+ cd application && npm audit --omit=dev
+ cd application && npm run humanize:fix
+ cd application && npm run lint:fix
+ $(MAKE) linter-fix
+
+build: ## Build libs and applications
+ cd application && npm run build
+
+test: ## Run tests
+ cd application && npm run test:ci
+ cd .github/actions/generate-blog-post && npm run test:ci
+ cd .github/actions/generate-brand-content && npm run test:ci
+ cd .github/actions/validate-manifest && npm run test:ci
+
+ci: ## Run tests in CI mode
+ $(MAKE) lint-fix
+ $(MAKE) build
+ $(MAKE) test
+
+linter-fix: ## Execute linting and fix
+ $(call run_linter, \
+ -e FIX_CSS_PRETTIER=true \
+ -e FIX_JSON_PRETTIER=true \
+ -e FIX_JAVASCRIPT_PRETTIER=true \
+ -e FIX_YAML_PRETTIER=true \
+ -e FIX_MARKDOWN=true \
+ -e FIX_MARKDOWN_PRETTIER=true \
+ -e FIX_NATURAL_LANGUAGE=true)
+
+define run_linter
+ DEFAULT_WORKSPACE="$(CURDIR)"; \
+ LINTER_IMAGE="linter:latest"; \
+ VOLUME="$$DEFAULT_WORKSPACE:$$DEFAULT_WORKSPACE"; \
+ docker build --build-arg UID=$(shell id -u) --build-arg GID=$(shell id -g) --tag $$LINTER_IMAGE .; \
+ docker run \
+ -v $$VOLUME \
+ --rm \
+ -w "$$DEFAULT_WORKSPACE" \
+ -e DEFAULT_WORKSPACE="$$DEFAULT_WORKSPACE" \
+ -e GITHUB_WORKSPACE="$$DEFAULT_WORKSPACE" \
+ -e FILTER_REGEX_INCLUDE="$(filter-out $@,$(MAKECMDGOALS))" \
+ -e VALIDATE_TYPESCRIPT_PRETTIER=false \
+ -e VALIDATE_TYPESCRIPT_ES=false \
+ -e VALIDATE_CSS=false \
+ $(1) \
+ $$LINTER_IMAGE
+endef
+
+define docker-compose
+ COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml -f docker-compose.local.yml -f docker-compose.$(1).yml $(2)
+endef
+
+define open-in-browser
+ @if command -v x-www-browser &> /dev/null ; then x-www-browser $(1); \
+ elif command -v xdg-open &> /dev/null ; then xdg-open $(1); \
+ elif command -v open &> /dev/null ; then open $(1); \
+ elif command -v start &> /dev/null ; then start $(1); fi;
+endef
+
+#############################
+# Argument fix workaround
+#############################
+%:
+ @:
diff --git a/README.md b/README.md
index d2660c8..01b0501 100644
--- a/README.md
+++ b/README.md
@@ -1,179 +1,119 @@
-# Cloud Native Provence - Landing page
+# Cloud Native Provence — Landing Page
-### Project structure
+Astro-based conference website with bilingual routing (`fr` / `en`) and translated slugs.
+## Development workflow (Makefile-first)
+
+Use `make` commands from the repository root.
+
+```bash
+make help
```
-/
-└── application/
- ├── public/
- │ ├── _headers
- │ └── robots.txt
- ├── src/
- │ ├── assets/
- │ │ ├── favicons/
- │ │ ├── images/
- │ │ └── styles/
- │ │ └── tailwind.css
- │ ├── components/
- │ │ ├── blog/
- │ │ ├── common/
- │ │ ├── ui/
- │ │ ├── widgets/
- │ │ │ ├── Header.astro
- │ │ │ └── ...
- │ │ ├── CustomStyles.astro
- │ │ ├── Favicons.astro
- │ │ └── Logo.astro
- │ ├── content/
- │ │ ├── post/
- │ │ │ ├── post-slug-1.md
- │ │ │ ├── post-slug-2.mdx
- │ │ │ └── ...
- │ │ └-- config.ts
- │ ├── layouts/
- │ │ ├── Layout.astro
- │ │ ├── MarkdownLayout.astro
- │ │ └── PageLayout.astro
- │ ├── pages/
- │ │ ├── [...blog]/
- │ │ │ ├── [category]/
- │ │ │ ├── [tag]/
- │ │ │ ├── [...page].astro
- │ │ │ └── index.astro
- │ │ ├── index.astro
- │ │ ├── 404.astro
- │ │ ├-- rss.xml.ts
- │ │ └── ...
- │ ├── utils/
- │ ├── config.yaml
- │ └── navigation.js
- ├── package.json
- ├── astro.config.ts
- └── ...
-```
-Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
-
-There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
-
-Any static assets, like images, can be placed in the `public/` directory if they do not require any transformation or in the `assets/` directory if they are imported directly.
-
-
-
-### Commands
-
-All commands are run from the `application` folder, from a terminal:
-
-| Command | Action |
-| :------------------ | :------------------------------------------------- |
-| `npm install` | Installs dependencies |
-| `npm run dev` | Starts local dev server at `localhost:4321` |
-| `npm run build` | Build your production site to `./dist/` |
-| `npm run preview` | Preview your build locally, before deploying |
-| `npm run check` | Check your project for errors |
-| `npm run fix` | Run Eslint and format codes with Prettier |
-| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
-
-
-
-### Configuration
-
-Basic configuration file: `./src/config.yaml`
-
-```yaml
-site:
- name: "Example"
- site: "https://example.com"
- base: "/" # Change this if you need to deploy to Github Pages, for example
- trailingSlash: false # Generate permalinks with or without "/" at the end
-
- googleSiteVerificationId: false # Or some value,
-
-# Default SEO metadata
-metadata:
- title:
- default: "Example"
- template: "%s — Example"
- description: "This is the default meta description of Example website"
- robots:
- index: true
- follow: true
- openGraph:
- site_name: "Example"
- images:
- - url: "~/assets/images/default.png"
- width: 1200
- height: 628
- type: website
- twitter:
- handle: "@twitter_user"
- site: "@twitter_user"
- cardType: summary_large_image
-
-i18n:
- language: en
- textDirection: ltr
-
-apps:
- blog:
- isEnabled: true # If the blog will be enabled
- postsPerPage: 6 # Number of posts per page
-
- post:
- isEnabled: true
- permalink: "/blog/%slug%" # Variables: %slug%, %year%, %month%, %day%, %hour%, %minute%, %second%, %category%
- robots:
- index: true
-
- list:
- isEnabled: true
- pathname: "blog" # Blog main path, you can change this to "articles" (/articles)
- robots:
- index: true
-
- category:
- isEnabled: true
- pathname: "category" # Category main path /category/some-category, you can change this to "group" (/group/some-category)
- robots:
- index: true
-
- tag:
- isEnabled: true
- pathname: "tag" # Tag main path /tag/some-tag, you can change this to "topics" (/topics/some-category)
- robots:
- index: false
-
- isRelatedPostsEnabled: true # If a widget with related posts is to be displayed below each post
- relatedPostsCount: 4 # Number of related posts to display
-
-analytics:
- vendors:
- googleAnalytics:
- id: null # or "G-XXXXXXXXXX"
-
-ui:
- theme: "system" # Values: "system" | "light" | "dark" | "light:only" | "dark:only"
+Main targets:
+
+- `make setup` — install project dependencies
+- `make start` — start local dev server (`application`)
+- `make build` — production build
+- `make lint` — run linters/checks
+- `make lint-fix` — run fixers
+- `make ci` — lint + build + test pipeline
+
+Do not run `npm run ...` directly unless you are explicitly debugging Makefile behavior.
+
+## Dev Container
+
+This repository includes a VS Code Dev Container in `.devcontainer/devcontainer.json`.
+
+### Recommended usage
+
+1. Open the repository in VS Code.
+2. Run `Dev Containers: Reopen in Container`.
+3. Once inside the container, run:
+
+```bash
+make setup
+make start
```
-
+The dev container provides:
-#### Customize Design
+- Node.js
+- Docker-in-Docker
+- GitHub CLI
+- VS Code extensions for Astro, ESLint, Prettier, Tailwind, Makefile, Copilot
-To customize Font families, Colors or more Elements refer to the following files:
+The app is available on port `4321`.
-- `src/components/CustomStyles.astro`
-- `src/assets/styles/tailwind.css`
+## Current routing model
-### Deploy
+Routing is locale-first and centralized.
-#### Deploy to production (manual)
+- `/` redirects to `/fr`
+- `/fr` and `/en` are localized homepages
+- `/{lang}/{translated-slug}` for content pages
-You can create an optimized production build with:
+Key files:
-```shell
-npm run build
+- `application/src/pages/index.astro` — root redirect
+- `application/src/pages/[lang]/index.astro` — localized homepage
+- `application/src/pages/[lang]/[page].astro` — localized dynamic pages
+- `application/src/i18n/routes.ts` — route slug mapping + path helpers
+
+Example translated slugs:
+
+- FR: `/fr/a-propos`, `/fr/charte-graphique`, `/fr/politique-de-confidentialite`
+- EN: `/en/about`, `/en/brand-guidelines`, `/en/privacy-policy`
+
+## Project structure (current)
+
+```text
+/
+├── .devcontainer/
+├── Makefile
+├── Dockerfile
+├── README.md
+└── application/
+ ├── astro.config.ts
+ ├── package.json
+ ├── public/
+ │ ├── _headers
+ │ ├── robots.txt
+ │ └── .well-known/appspecific/com.chrome.devtools.json
+ └── src/
+ ├── assets/
+ ├── components/
+ ├── i18n/
+ │ ├── ui.ts
+ │ ├── utils.ts
+ │ └── routes.ts
+ ├── layouts/
+ ├── pages/
+ │ ├── [lang]/
+ │ │ ├── index.astro
+ │ │ └── [page].astro
+ │ ├── [...blog]/
+ │ ├── home/
+ │ ├── about/
+ │ ├── contact/
+ │ ├── sponsoring/
+ │ ├── brand-guidelines/
+ │ ├── terms/
+ │ ├── privacy/
+ │ ├── index.astro
+ │ ├── rss.xml.ts
+ │ └── 404.astro
+ └── utils/
```
-Now, your website is ready to be deployed. All generated files are located at
-`dist` folder, which you can deploy the folder to any hosting service you
-prefer.
+## Configuration
+
+Primary site configuration: `application/src/config.yaml`.
+
+Astro framework configuration: `application/astro.config.ts`.
+
+## Build output
+
+Production build output is generated in:
+
+- `application/dist/`