diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..70e4e0488b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# EditorConfig helps maintain consistent coding styles +# https://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +# Trailing whitespace is significant in Markdown (line breaks) +trim_trailing_whitespace = false + +[*.{yaml,yml}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..c197ed6e58 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,43 @@ +## Summary + + + +## Changes + + + +## Languages affected + + +- [ ] English +- [ ] Brazilian Portuguese (`content/brazilian-portuguese/`) +- [ ] Spanish (`content/espanol/`) +- [ ] Korean (`content/korean/`) +- [ ] Other: ___ + +## Checklist + + + +### Required +- [ ] Hugo builds without errors (`hugo --quiet`) +- [ ] All image references use **lowercase** extensions (`.png`, `.jpg`, `.gif` — not `.PNG`) +- [ ] All `` tags have quoted attributes and `alt` text +- [ ] YAML frontmatter has no duplicate keys +- [ ] If I changed English content, I checked for matching translations and either fixed them too or noted which ones need updating + +### If adding images +- [ ] Images are under 500 KB (compress large screenshots) +- [ ] Image filenames are descriptive (not `image1.png`) +- [ ] Images have `width` attributes to control sizing + +### If adding a new workshop +- [ ] Workshop follows the standard folder structure (see CONTRIBUTING.md) +- [ ] `_index.md` has correct frontmatter (title, description, prereq, difficulty, icon, draft) +- [ ] Table of contents uses `
/` pattern +- [ ] All activities are numbered with correct `weight` values + +### If translating +- [ ] Translated ALL user-visible text (titles, descriptions, button labels) +- [ ] Kept image paths, code blocks, and shortcodes unchanged +- [ ] YAML frontmatter keys are in English (only values are translated) diff --git a/.github/workflows/content-quality.yaml b/.github/workflows/content-quality.yaml new file mode 100644 index 0000000000..83bca0bc5b --- /dev/null +++ b/.github/workflows/content-quality.yaml @@ -0,0 +1,192 @@ +name: Content Quality Checks + +on: + pull_request: + paths: + - 'content/**' + - 'layouts/**' + - 'static/**' + - 'themes/**' + - 'config.toml' + +jobs: + hugo-build: + name: Hugo Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: 'latest' + extended: true + + - name: Build site + run: hugo --quiet --minify + + content-lint: + name: Content Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Get changed files + id: changed + uses: tj-actions/changed-files@v44 + with: + files: | + content/**/*.md + + - name: Check uppercase image extensions + if: steps.changed.outputs.any_changed == 'true' + run: | + echo "🔍 Checking for uppercase image extensions (.PNG, .JPG, .GIF)..." + ERRORS=0 + for file in ${{ steps.changed.outputs.all_changed_files }}; do + if [ -f "$file" ]; then + MATCHES=$(grep -n '\.\(PNG\|JPG\|GIF\|JPEG\|BMP\)' "$file" || true) + if [ -n "$MATCHES" ]; then + echo "❌ $file:" + echo "$MATCHES" + ERRORS=$((ERRORS + 1)) + fi + fi + done + if [ $ERRORS -gt 0 ]; then + echo "" + echo "âš ī¸ Found $ERRORS file(s) with uppercase image extensions." + echo " Linux servers are case-sensitive — use .png, .jpg, .gif instead." + exit 1 + fi + echo "✅ No uppercase image extensions found." + + - name: Check unquoted HTML attributes + if: steps.changed.outputs.any_changed == 'true' + run: | + echo "🔍 Checking for unquoted HTML attributes..." + ERRORS=0 + for file in ${{ steps.changed.outputs.all_changed_files }}; do + if [ -f "$file" ]; then + # Match src= or href= followed by a space then non-quote char (unquoted attr) + MATCHES=$(grep -n 'src= [^"]' "$file" || true) + if [ -n "$MATCHES" ]; then + echo "❌ $file:" + echo "$MATCHES" + ERRORS=$((ERRORS + 1)) + fi + fi + done + if [ $ERRORS -gt 0 ]; then + echo "" + echo "âš ī¸ Found $ERRORS file(s) with unquoted HTML attributes." + echo " Always quote attributes: src=\"file.png\" not src= file.png" + exit 1 + fi + echo "✅ No unquoted HTML attributes found." + + - name: Check duplicate YAML frontmatter keys + if: steps.changed.outputs.any_changed == 'true' + run: | + echo "🔍 Checking for duplicate YAML frontmatter keys..." + ERRORS=0 + for file in ${{ steps.changed.outputs.all_changed_files }}; do + if [ -f "$file" ]; then + # Extract frontmatter (between --- markers), get keys, find duplicates + DUPES=$(sed -n '/^---$/,/^---$/p' "$file" | grep -E '^\w+:' | sed 's/:.*//' | sort | uniq -d) + if [ -n "$DUPES" ]; then + echo "❌ $file has duplicate YAML keys:" + echo "$DUPES" + ERRORS=$((ERRORS + 1)) + fi + fi + done + if [ $ERRORS -gt 0 ]; then + echo "" + echo "âš ī¸ Found $ERRORS file(s) with duplicate YAML frontmatter keys." + echo " Hugo silently uses the last value — remove duplicates." + exit 1 + fi + echo "✅ No duplicate YAML keys found." + + - name: Check images have alt text + if: steps.changed.outputs.any_changed == 'true' + run: | + echo "🔍 Checking for images without alt text..." + WARNINGS=0 + for file in ${{ steps.changed.outputs.all_changed_files }}; do + if [ -f "$file" ]; then + # Check for ![]( (empty alt text in markdown images) + MATCHES=$(grep -n '!\[\](' "$file" || true) + if [ -n "$MATCHES" ]; then + echo "âš ī¸ $file has images without alt text:" + echo "$MATCHES" + WARNINGS=$((WARNINGS + 1)) + fi + # Check for tags without alt attribute + IMG_NO_ALT=$(grep -n ' +Table of Contents +{{% children /%}} +
+``` + +## Content guidelines + +### Images + +- **Lowercase extensions only**: `.png`, `.jpg`, `.gif` — never `.PNG` or `.JPG` (Linux servers are case-sensitive) +- **Descriptive filenames**: `replit-upload-dialog.png` not `image1.png` +- **Always include alt text**: `![Student typing code in Replit editor](media/replit-editor.png)` +- **Control image sizing**: `![Description](media/screenshot.png?width=60%)` or use HTML with `width` attribute +- **Keep images under 500 KB** — compress screenshots with tools like [TinyPNG](https://tinypng.com) + +### HTML in markdown + +If you use raw HTML tags in markdown files: + +- Quote all attributes: `src="file.png"` not `src=file.png` +- Close all tags properly +- Include `alt` text on all `` tags + +### YAML frontmatter + +- No duplicate keys (Hugo will silently use the last value) +- Keep keys in English — only translate values +- Use `draft: true` for work-in-progress content + +### Code blocks + +Use fenced code blocks with language hints: + +````markdown +```python +print("Hello, world!") +``` +```` + +## Translations + +We love translations! Here's how to do them well: + +### What to translate +- All user-visible text (titles, descriptions, instructions, button labels) +- YAML frontmatter values (`title`, `description`, `prereq`, `difficulty`) +- Alt text on images + +### What NOT to translate +- YAML frontmatter keys (`title:`, `weight:`, `draft:`) +- Code inside code blocks +- Hugo shortcodes (`{{% notice %}}`, `{{% children /%}}`) +- File paths and image references +- Variable names and function names + +### Translation checklist +1. Create your files under `content/{language}/{workshop-name}/` +2. Keep the same file structure as the English version +3. If the workshop uses images with English text, note which ones need localized versions +4. Test that Hugo builds successfully with your translation + +### Supported languages + +| Folder | Language | +|--------|----------| +| `english` | English | +| `espanol` | Spanish | +| `brazilian-portuguese` | Brazilian Portuguese | +| `korean` | Korean | +| `francais` | French | +| `german` | German | +| `simplified-chinese` | Simplified Chinese | +| `traditional-chinese` | Traditional Chinese | +| `kyrgyz` | Kyrgyz | + +## Testing locally + +### Prerequisites +- [Hugo Extended](https://gohugo.io/installation/) (the extended version is required for SCSS) +- Git + +### Run locally + +```bash +git clone https://github.com/NuevoFoundation/workshops.git +cd workshops +git submodule update --init --recursive +hugo server +``` + +Visit `http://localhost:1313` to preview. + +### Verify your changes + +Before opening a PR: + +```bash +# Build the site — should complete with zero errors +hugo --quiet + +# Check for uppercase image extensions +grep -rn '\.\(PNG\|JPG\|GIF\|JPEG\)' content/ --include="*.md" + +# Check for unquoted HTML attributes (common in img tags) +grep -rn 'src= [^"]' content/ --include="*.md" +``` + +## PR guidelines + +- Keep PRs focused: one fix or one feature per PR +- Use the PR template checklist +- If your PR fixes an issue, reference it: "Closes #123" +- For translations: include all files for one complete workshop per PR + +## Questions? + +Open an issue or reach out to the maintainers. We're happy to help! 🚀