Skip to content

Commit ff4dfa5

Browse files
doodlewindclaude
andcommitted
feat: scaffold Typed French — French grammar as TypeScript types
Fork of typed-japanese, re-themed "Crème & Bleu de France" ⚜️. - src/: Typed French type DSL — nouns+gender, articles (le/la/l'/un/une/du), adjective agreement, pronouns, and verb conjugation for the three regular groups (-er/-ir/-re) plus irregulars (être, avoir, aller, faire, …), all resolved with template-literal + conditional types. Type-level tests pass. - playground/: Vite + React + Tailwind v4 + Monaco, SSR-prerendered to static HTML per route (EN / 简体中文). Five tabs: Foundations, Grammar Course, Glossary, Playground, Eval. - analysis/: parser + resolver adapted to space-delimited French and the new DSL wrapper names. - Seed content: chapters e01 (gender & articles) and e02 (-er present), one Foundations article, and a starter glossary. verify:snippets + verify:vocab + full build all green. - Deploy: GitHub Pages via .github/workflows, VITE_BASE=/typed-french/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 parents  commit ff4dfa5

95 files changed

Lines changed: 10862 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/scheduled_tasks.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"sessionId":"b44693ac-c44f-41f6-8e26-d9477db10de9","pid":96057,"procStart":"Sat Jun 20 15:00:34 2026","acquiredAt":1781968542718}

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
verify:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v3
15+
16+
- name: Install pnpm
17+
uses: pnpm/action-setup@v4
18+
with:
19+
version: 9
20+
21+
- name: Set up Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '22'
25+
cache: 'pnpm'
26+
27+
- name: Install dependencies
28+
run: pnpm install
29+
30+
- name: Run test
31+
run: pnpm run test
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Deploy Playground
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
# Allow one concurrent deployment, cancelling in-progress runs.
14+
concurrency:
15+
group: pages
16+
cancel-in-progress: true
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
defaults:
22+
run:
23+
working-directory: playground
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- name: Install pnpm
28+
uses: pnpm/action-setup@v4
29+
with:
30+
version: 9
31+
32+
- name: Set up Node.js
33+
uses: actions/setup-node@v4
34+
with:
35+
node-version: "22"
36+
cache: pnpm
37+
cache-dependency-path: playground/pnpm-lock.yaml
38+
39+
- name: Install dependencies
40+
run: pnpm install
41+
42+
- name: Build
43+
run: pnpm build
44+
45+
- name: Upload artifact
46+
uses: actions/upload-pages-artifact@v3
47+
with:
48+
path: playground/dist
49+
50+
deploy:
51+
needs: build
52+
runs-on: ubuntu-latest
53+
environment:
54+
name: github-pages
55+
url: ${{ steps.deployment.outputs.page_url }}
56+
steps:
57+
- name: Deploy to GitHub Pages
58+
id: deployment
59+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
node_modules
3+
dist
4+
dist-server
5+
*.tsbuildinfo

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2025-present, Yifeng Wang
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
13+
all 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
21+
THE SOFTWARE.

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# ⚜️ Typed French
2+
3+
> Learn French grammar as TypeScript types — grammar you can verify.
4+
> 用 TypeScript 类型学法语语法 —— 可被编译器验证的语法。
5+
6+
**Typed French** encodes French grammar — gender, articles, agreement, and verb
7+
conjugation — as TypeScript types. A French sentence becomes a type expression
8+
whose value the compiler *computes*: `Le chat est noir.` is not a string you
9+
wrote, it's the result the type checker resolves. If the grammar is wrong, the
10+
code doesn't compile.
11+
12+
It is the French sibling of
13+
[`typed-japanese`](https://github.com/typedgrammar/typed-japanese), sharing the
14+
same architecture and design-token system, dressed in a **Crème & Bleu de
15+
France** theme.
16+
17+
## What's inside
18+
19+
| Part | Path | What it is |
20+
| --- | --- | --- |
21+
| **Grammar library** | [`src/`](./src) | The Typed French type DSL: nouns + gender, articles, adjective agreement, pronouns, and verb conjugation (3 groups + irregulars) — all resolved with template-literal + conditional types. |
22+
| **Foundations** | playground · *Foundations* | Concept articles on the architecture of French (gender, elision, agreement). |
23+
| **Grammar Course** | playground · *Grammar Course* | A bilingual (EN / 简体中文) chapter-by-chapter syllabus where every example is a self-contained, type-checked snippet. |
24+
| **Glossary** | playground · *Glossary* | Every word the course uses, with gender, part of speech and meaning, cross-linked to the sentences that reference it. |
25+
| **Playground** | playground · *Playground* | An in-browser Monaco editor: write a French sentence as a type and watch the compiler resolve and visualise its structure. |
26+
27+
## The idea in 30 seconds
28+
29+
```ts
30+
import type {
31+
CommonNoun, DefiniteNP, Adjective, ConjugateAdjective,
32+
IrregularVerb, ConjugateVerb, Sentence,
33+
} from "typed-french";
34+
35+
type chat = CommonNoun<"chat", "m">; // a masculine noun
36+
type être = IrregularVerb<"être">;
37+
type noir = Adjective<"noir">;
38+
39+
// The compiler resolves this to the string "Le chat est noir."
40+
type Phrase = Sentence<`${DefiniteNP<chat>} ${ConjugateVerbtre, "Present", "il">} ${ConjugateAdjective<noir, "m">}`>;
41+
```
42+
43+
`DefiniteNP<chat>` reads the noun's gender and produces `le chat`; for a
44+
feminine or vowel-initial noun it would produce `la …` or `l'…`. Conjugation and
45+
adjective agreement work the same way — pure type-level computation.
46+
47+
## Develop
48+
49+
```bash
50+
# the grammar library (type-checks the .d.ts DSL + examples)
51+
pnpm install
52+
pnpm test # typecheck + lint
53+
54+
# the playground (Vite + React + Tailwind v4 + Monaco)
55+
cd playground
56+
pnpm install
57+
pnpm dev # http://localhost:5173
58+
pnpm build # static build → playground/dist (prerendered per route)
59+
pnpm verify:snippets # every course snippet type-checks AND resolves to its sentence
60+
pnpm verify:vocab # every word the course uses is in the glossary
61+
```
62+
63+
## Deployment
64+
65+
The playground is a statically prerendered SPA deployed to **GitHub Pages** via
66+
[`.github/workflows/deploy-playground.yml`](./.github/workflows/deploy-playground.yml)
67+
on every push to `main`. `VITE_BASE` is `/typed-french/`.
68+
69+
## Design
70+
71+
The theme — *Crème & Bleu de France* — is documented in
72+
[`playground/DESIGN.md`](./playground/DESIGN.md). Tokens live as CSS custom
73+
properties in `playground/src/theme.css` and are mapped into Tailwind v4 with
74+
`@theme inline`; they are intended to be extracted into a shared design-token
75+
package across the Typed Grammar family.
76+
77+
## License
78+
79+
MIT

eslint.config.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import tseslint from '@typescript-eslint/eslint-plugin';
2+
import tseslintParser from '@typescript-eslint/parser';
3+
import js from '@eslint/js';
4+
5+
export default [
6+
{
7+
// playground/ is a standalone Vite app and apps/ is the TypedTranslate macOS
8+
// app (bun bridge scripts) — both have their own tsconfig/runtime and are not
9+
// part of this package's lint/compile project.
10+
ignores: ['node_modules/**', 'dist/**', 'playground/**', 'apps/**']
11+
},
12+
js.configs.recommended,
13+
{
14+
files: ['**/*.ts', '**/*.tsx'],
15+
languageOptions: {
16+
parser: tseslintParser,
17+
parserOptions: {
18+
project: './tsconfig.json'
19+
},
20+
globals: {
21+
console: 'readonly'
22+
}
23+
},
24+
plugins: {
25+
'@typescript-eslint': tseslint
26+
},
27+
rules: {
28+
'@typescript-eslint/consistent-type-imports': ['error', {
29+
prefer: 'type-imports',
30+
disallowTypeAnnotations: true,
31+
fixStyle: 'separate-type-imports'
32+
}],
33+
'sort-imports': ['error', {
34+
ignoreCase: false,
35+
ignoreDeclarationSort: false,
36+
ignoreMemberSort: false,
37+
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single']
38+
}],
39+
'no-unused-vars': 'off',
40+
'@typescript-eslint/no-unused-vars': 'off'
41+
}
42+
},
43+
{
44+
files: ['**/examples/**/*.ts'],
45+
rules: {
46+
'sort-imports': 'off'
47+
}
48+
}
49+
];

package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "@typedgrammar/typed-french",
3+
"version": "0.1.0",
4+
"description": "TypeScript types for French grammar",
5+
"type": "module",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"scripts": {
9+
"typecheck": "tsc --noEmit",
10+
"build": "tsc",
11+
"lint": "eslint . --ext .ts,.tsx",
12+
"test": "pnpm typecheck && pnpm lint"
13+
},
14+
"keywords": [
15+
"typescript",
16+
"french",
17+
"grammar",
18+
"types"
19+
],
20+
"author": "",
21+
"license": "MIT",
22+
"devDependencies": {
23+
"@eslint/js": "^9.39.4",
24+
"@typescript-eslint/eslint-plugin": "^8.61.0",
25+
"@typescript-eslint/parser": "^8.61.0",
26+
"eslint": "^9.39.4",
27+
"typescript": "^5.9.3"
28+
}
29+
}

playground/DESIGN.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Design System — Crème & Bleu de France ⚜️
2+
3+
> Category: Education · Developer Tools
4+
>
5+
> An editorial design language for Typed French. Warm cream paper (papier crème),
6+
> deep bleu-nuit ink, a single muted bleu-de-France accent, a touch of rouge and
7+
> a filet doré (gold hairline) — the tricolore *en sourdine*. The same token
8+
> architecture dresses the whole Typed Grammar family; only the palette changes.
9+
10+
This document is the source of truth. The canonical token implementation lives in
11+
[`src/theme.css`](./src/theme.css) (CSS custom properties, light under `:root`, dark
12+
under `:root[data-theme="dark"]`), mapped into Tailwind v4 with `@theme inline`.
13+
Authored against the [Open Design](https://github.com/nexu-io/open-design) 9-section schema.
14+
15+
## 1. Color
16+
17+
The palette is three ideas: **crème** (paper) for surfaces, **encre bleu-nuit**
18+
(ink) for text, and **bleu de France** as the one accent — a muted, editorial
19+
blue, never the saturated flag. A **rouge** completes the tricolore as a second
20+
accent (and the error colour), and a **filet doré** adds a discreet gilded note.
21+
22+
| Role | Token | Light | Dark |
23+
| --- | --- | --- | --- |
24+
| Page | `--paper` | `#f7f4ec` | `#12161f` |
25+
| Card surface | `--surface` | `#fffdf8` | `#1a1f2b` |
26+
| Subtle fill | `--surface-2` | `#eef0f6` | `#232a39` |
27+
| Hairline | `--border` / `--border-strong` | `#e7e2d4` / `#d8d0bd` | `#2c3340` / `#3d4656` |
28+
| Text | `--ink-900``--ink-300` | `#1c2433``#9aa1b1` | `#eef1f7``#757d8d` |
29+
| Accent | `--azur-500` | `#3b5ba5` | `#8fb0e6` |
30+
| Accent deep / link | `--azur-600` | `#2f4a88` | `#aac4f0` |
31+
| On-accent text | `--on-accent` | `#ffffff` | `#12161f` |
32+
| Rouge / Gold | `--rouge` / `--gold` | `#b23a48` / `#b8922f` | `#f0788a` / `#d8b85f` |
33+
| Success / error | `--ok` / `--err` | `#2f7d5b` / `#b23a48` | `#4cc08c` / `#f0788a` |
34+
35+
The azur ramp (`--azur-50``--azur-600`) **inverts** between modes: in light, 50
36+
is the faintest wash and 600 the deepest link; in dark, 50 becomes the deepest fill
37+
and 600 the brightest, with 500 staying the accent in both. This keeps semantic
38+
usage (`background: --azur-100`, `color: --azur-600`) correct without per-component
39+
dark rules.
40+
41+
Grammar-category accents (`--cat-*`, used by the composition tree) keep distinct
42+
hues but ship a lightened/desaturated dark variant so they stay legible on bleu-nuit.
43+
44+
**Always reference tokens, never raw hex.** Text-on-accent uses `--on-accent` (it
45+
flips to dark ink in dark mode, where the accent is a light azur). Soft status
46+
backgrounds use `--ok-soft` / `--err-soft`.
47+
48+
## 2. Typography
49+
50+
- **UI**`--font-ui`: EB Garamond + Noto Serif SC (for 简体中文). Warm, editorial.
51+
- **French**`--font-fr`: EB Garamond, applied automatically via `:lang(fr)` and
52+
`.fr`; example sentences render larger.
53+
- **Headings**`--font-heading`: Playfair Display — the Parisian display sheen.
54+
- **Mono**`--font-mono`: SF Mono / JetBrains Mono. Inline code (`.tj-code`) and the editor.
55+
56+
Weights: body 400, labels/UI 600–700, titles 800. Section labels 0.72rem, uppercase,
57+
`letter-spacing: 0.04em`, `--ink-500`. Body line-height ~1.7 for prose.
58+
59+
## 3. Spacing
60+
61+
A soft 4px rhythm (gaps of 6/8/10/12/16/20px). Cards pad 12–20px; page gutters 20px;
62+
max content width 1280px. Prose columns cap at ~72ch.
63+
64+
## 4. Layout
65+
66+
- App shell: centered column, 1280px max, 20px gutter (`.app`).
67+
- Course: sticky 268px sidebar + fluid content; collapses to one column < 900px.
68+
- Analyzer: editor | tree, two panes; opens in a right-side drawer over a `--scrim`.
69+
70+
## 5. Components
71+
72+
Shared primitives live as `.tj-*` utilities in `theme.css`:
73+
74+
- `.tj-card` — surface + hairline + `--shadow-sm`, `--radius` (14px).
75+
- `.tj-btn` / `.tj-btn--primary` — pill buttons; primary fills `--azur-500` with `--on-accent`.
76+
- `.tj-chip` — rounded tag, `--surface-2` fill, `--azur-600` text.
77+
- `.tj-input` / `.tj-select` — focus shows `--azur-400` border + a 3px `--ring` halo.
78+
- `.tj-label`, `.tj-subtle`, `.tj-result`, `.tj-code`.
79+
80+
Active/selected states fill `--azur-500`. Radii: 14px cards, 9px controls, 999px pills.
81+
82+
## 6. Motion
83+
84+
Restrained and short. Color/border transitions 0.12–0.2s ease. Drawer slides in
85+
0.26s `cubic-bezier(0.4,0,0.2,1)`. Respect `prefers-reduced-motion`.
86+
87+
## 7. Voice
88+
89+
Calm, precise, encouraging — a good programming tutorial, not a textbook. Bilingual
90+
EN / 简体中文 at equal depth. Concepts are motivated before mechanics. Let the type
91+
checker make the claims.
92+
93+
## 8. Brand
94+
95+
The mark is the fleur-de-lis (⚜️). The wordmark "Typed French" / "TypedGrammar" uses
96+
an azur-600 → azur-400 gradient on the title. The throughline: *grammar you can
97+
verify* — every sentence is a type the compiler reads.
98+
99+
## 9. Anti-patterns
100+
101+
- ❌ Saturated flag blue/red — reads cheap. Bleu de France is muted, editorial.
102+
- ❌ Raw hex / `rgba()` in component CSS — breaks dark mode. Use tokens.
103+
- ❌ White text hardcoded on accents — use `--on-accent`.
104+
- ❌ Heavy drop shadows or glows — keep elevation soft (`--shadow-sm` / `--shadow-md`).
105+
- ❌ Theme set after mount (causes a flash) — resolve `data-theme` before first paint.

0 commit comments

Comments
 (0)