diff --git a/.agents/skills/identify-gaps/SKILL.md b/.agents/skills/identify-gaps/SKILL.md new file mode 100644 index 00000000000..8337ec6b00b --- /dev/null +++ b/.agents/skills/identify-gaps/SKILL.md @@ -0,0 +1,98 @@ +--- +name: identify-gaps +description: + Reviews OpenFn docs pages and reports what to fix, marked Must, Should, or + Could change. Checks accuracy against the Lightning, kit, and adaptors code, + clarity for newcomers, missing coverage, and product changes. Does not edit + anything. Use when asked to review or audit the docs, check whether a page is + accurate or out of date, find gaps, or as the first step of "improve the + docs". +--- + +# Identify gaps + +Look at the docs and work out what should be improved. The output is a report of +recommendations. This skill does not edit anything; that is the job of +`update-content/SKILL.md`. + +## Input + +By default, the whole site. The user can narrow it to a section: one sidebar +category, one folder under `docs/`, or one page. + +You may also be handed context, such as the list of product changes from +`release-review/SKILL.md`. If so, focus on the pages that context points to. + +## Four ways to look + +For a general review, use all four. When you are given a list of changes, start +with the last one. + +### Is it accurate? + +Pick out the things a reader would act on: code samples, function names, flags, +button labels, defaults, versions, limits. Check each against the code: +`OpenFn/lightning` for the web app, `OpenFn/kit` for the CLI, `OpenFn/adaptors` +for adaptor functions. Note the file and line. Anything you cannot check from +code, such as pricing, policy, or per-deployment settings, is a question for the +product team. Try each external link twice: a 404 or 410 means it is dead, but a +403 or 429 does not. + +### Can a newcomer follow it? + +Read the page as someone who knows what an API, JSON, and a terminal are, but +has never heard of OpenFn. Try to do what it says. Note every place you had to +guess: an undefined term, a missing prerequisite, steps in the wrong order, no +way to tell you had succeeded. + +### What is missing? + +Compare what the docs cover with what the product has: commands, screens, +settings. Before calling something missing, search the whole site the way a user +would type it; it may be documented elsewhere. If you can see user evidence +(issues, forum posts), note how often the topic comes up. Do not invent demand. +Feature-flagged or deliberately hidden things are not gaps. A page that is in no +sidebar and linked from nowhere is an orphan; recommend adding or removing it, +since someone may be drafting it. + +### What has changed? + +For each product change you were given, search `docs/`, `articles/`, and +`adaptors/*.md` for it, using both the old and new names. Decide whether each +page it touches is now wrong or now incomplete. A change with no home in the +docs at all is a missing page. + +## House style + +In a general review, also list the pages that break the house style in +`AGENTS.md` or use a `variants` spelling from `glossary.yml`. These are Could +change. Group them by rule, with the pages under each, so one rule broken on 30 +pages is one recommendation, not 30. + +## Classify every recommendation + +- **Must change.** A reader following the page will fail or be misled: it + contradicts the code, the steps do not work, or it describes something that no + longer exists. +- **Should change.** The page works, but a newcomer will struggle or miss + something important: a missing prerequisite, an undefined term, a shipped + feature it does not mention. +- **Could change.** A nice improvement with little evidence of need: an extra + example, clearer wording, a small gap. + +If you are torn between two levels, pick the lower one. If you cannot decide +what is right (the docs and code disagree and either could be wrong), list it +under **Questions** instead. + +## The report + +Start with one line: what you covered, the repos and commits you checked, and +the count at each level. Then list Must, Should, Could, and Questions, one line +each: + +``` +[must] docs/build/triggers.md:42 — says the flag is -f; kit's cli.ts:88 says --force — change -f to --force +``` + +For a missing page, add where it should go and a short outline. Problems in +generated adaptor pages go in their own list, as issues for `OpenFn/adaptors`. diff --git a/.agents/skills/release-review/SKILL.md b/.agents/skills/release-review/SKILL.md new file mode 100644 index 00000000000..a54872842de --- /dev/null +++ b/.agents/skills/release-review/SKILL.md @@ -0,0 +1,56 @@ +--- +name: release-review +description: + Finds what shipped recently in OpenFn/lightning, OpenFn/kit, and + OpenFn/adaptors by reading their changelogs, then runs identify-gaps on the + docs those changes affect. Use when asked what shipped or changed this month, + whether the docs are up to date with a release, or for the monthly docs check. +--- + +# Release review + +Work out what the product shipped recently, then hand that list to +`identify-gaps/SKILL.md` to find the docs that need to catch up. + +By default, cover every release in the last month across `OpenFn/lightning`, +`OpenFn/kit`, and `OpenFn/adaptors`. Someone can narrow it to one repo, a date +range, a release tag, or a single PR. + +## Get the repos + +Clone the product repos you need outside this repo, with tags. Never change +them. If you are running inside a product repo instead, clone `OpenFn/docs` the +same way; docs changes always go in a PR on the docs repo. + +## Build the list of changes + +1. **Read the changelogs, not the diffs.** Lightning has one `CHANGELOG.md`, + with a date on each release. Kit and adaptors have one per package, under + `packages//CHANGELOG.md`. Kit's have no dates, so get them from the + release tags: + `git tag --sort=-creatordate --format='%(creatordate:short) %(refname:short)'`. + Read every entry released in the period. Skip the Unreleased section. + Lightning often lists a release's changes under its `-pre` heading, such as + `2.18.2-pre`, and leaves the final `2.18.2` empty, so read the `-pre` + entries too and report them under the final version. +2. **Rewrite each entry as a change a user would notice**: a new feature, a + renamed button, a new CLI flag, a changed default, a removed option. Drop + internal changes like refactors, dependency bumps, and tests. Open the linked + PR only if an entry is too vague. +3. **For adaptors, keep only two kinds of change**: a new adaptor, and a change + that could break a tutorial or guide. Adaptor function reference pages are + generated from code, so they update themselves. + +Each item on the list should give the repo, the release, what changed for the +user, and the old and new names if something was renamed. + +## Hand it on + +Run `identify-gaps/SKILL.md` with this list as its context. Put the list at the +top of the report, after one line saying which repos, releases, and dates you +covered. + +If nothing user-facing shipped in the period, say so and stop. + +If you were asked to update the docs as well, pass the report to +`update-content/SKILL.md`. diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md new file mode 100644 index 00000000000..e060e87589e --- /dev/null +++ b/.agents/skills/translate/SKILL.md @@ -0,0 +1,153 @@ +--- +name: translate +description: + Translates English docs pages into Spanish and French under i18n/, respecting + glossary.yml, translation-rules.yml, review status, and do-not-retranslate + fences, and opens one PR per locale. Use when asked to translate or refresh + translations. +disable-model-invocation: true +--- + +# Translate + +Translate English docs into Spanish (`es`) and French (`fr`). The English is +always the source of truth. Translations are generated files that live in +this repo. Save each one at the same path as the English page, under +`i18n//docusaurus-plugin-content-docs/current/`. For example, +`docs/build/triggers.md` goes to +`i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md`. +Docusaurus ignores a file anywhere else without an error, and the page stays +English. + +The sidebar headings come from `sidebars-main.js`, not from the pages. If it +has new or renamed entries, run +`yarn docusaurus write-translations --locale `. This adds them to +`i18n//docusaurus-plugin-content-docs/current.json` in English and +keeps the ones already translated. Translate the new ones. + +Never translate the generated adaptor pages, the job library, the old v1 +docs, or articles and blog posts. + +## Before you start + +Check these three things. If any fails, stop and ask. + +- The locale is enabled in `docusaurus.config.js`. Do not enable it yourself; + that changes what gets deployed. +- `i18n/` is not in `.gitignore`. +- `glossary.yml` and `translation-rules.yml` are valid YAML. + +Translate the English page as it is on disk after any fixes and after +Prettier has run, so the hash you record matches what you translated. + +## Front matter + +Copy the English page's front matter. Translate only `title` and +`sidebar_label`. Then add: + +```yaml +translation_source_hash: +translation_review_status: machine +``` + +The hash is the content hash of the English file, from +`git hash-object docs/.md`, not a commit. Commits do not survive squash +merges: a hash pointing at a commit made on a branch dangles as soon as the +branch is squashed onto main. A content hash is the same wherever the file +lives, and it answers the only question the field exists to answer: is the +English still the version this was translated from? To compare, hash the +current English file and check it against the recorded value. + +`translation_review_status` can be `machine`, `needs-review`, or +`human-reviewed`. Only a human ever sets `human-reviewed`, and when they do +they also add `translation_reviewer` and `translation_review_date`. + +## Decide what to do with each page + +- **No translation yet.** Translate the whole page. +- **The hash matches the current English file.** Skip it, whatever its + status. The English has not changed since it was translated. The one + exception: if `glossary.yml` or `translation-rules.yml` was committed more + recently than the translation (compare `git log -1 --format=%ct -- `), + treat a `machine` page as if the hash no longer matches, so it picks up the + new rules. +- **The hash no longer matches, and the status is `machine`, `needs-review`, + or missing.** Translate the whole page again, but keep any fenced blocks + (see below) exactly as they were. +- **The hash no longer matches, and the status is `human-reviewed`.** Leave + the file out of the translation PR. Instead, open a separate PR for the + named reviewer that changes only the affected parts. Recover the English the + reviewer saw with `git cat-file -p `, diff it against the + current English, and translate only what changed. In the same PR, set + `translation_source_hash` to the current English hash and leave the status + as `human-reviewed`: the reviewer merging it approves it. If the old version + is no longer in the repo, say so and offer a full retranslation in that PR + instead. + +## Fenced blocks + +A human can wrap part of a translation like this: + +```markdown + +Text a reviewer has corrected by hand. + +``` + +Copy those blocks into the new translation exactly, in the same place. If the +English they correspond to has been deleted, keep the block anyway and ask +what to do with it. + +## How to translate + +- Words in `glossary.yml` stay in English. For ordinary words that are also + product terms, like "run" or "step", keep the English only when the word + means the OpenFn thing. +- Follow any rules for the locale in `translation-rules.yml`. By default, + Spanish uses "tú" and French uses "vous". +- Copy code blocks and inline code exactly. You may translate comments inside + code. +- Keep the names of things in the app, like buttons, menus, tabs, and field + labels, exactly as they are in the English. The app is English only, so a + translated button name points the reader at a button that does not exist. +- Keep the same structure: same headings at the same levels, same lists, + same callouts, same components. +- Keep internal links as they are in the English. Do not add `/es/` or + `/fr/`; Docusaurus adds the locale when it builds the page. +- The one exception: a relative link like `../deploy/portability.md` breaks + if the page it points to has no translation yet. Write it as the page's + full address instead, like `/documentation/deploy/portability`. If the + target page sets a `slug` in its front matter, the address is + `/documentation` plus the slug: `slug: /api-tokens` gives + `/documentation/api-tokens`, not the folder path. +- Give translated headings the original English anchor so existing links + still work. + +## Before you commit + +Check that the fixed glossary terms (the ones without `product_noun: true`, +such as OpenFn, Lightning, adaptor, webhook) appear as many times as in the +English. Product nouns like "run" and "step" are allowed to differ, since +their ordinary-English uses get translated. Before counting, join each file +into one line with single spaces: Prettier wraps prose at 80 columns, and +English and Spanish wrap at different points, so a multi-word term like "work +order" can sit across a line break in one file and not the other. Check the +code blocks are identical. Check the counts of headings, code blocks, +callouts, images, and tables match. Check the front matter is complete. Check +every fenced block survived. Then build the site and make sure it passes: + +```bash +yarn generate-library +yarn generate-adaptors +yarn build +``` + +Build the whole site, not just your locale. `yarn build --locale ` +builds the locale at the site root, so every correct `/es/...` link shows up +as broken. + +Open one PR per locale, separate from the English PR. Translated files do not +count toward the 20-file limit, because a locale's translations are reviewed +as a set. In the PR description, say which tool and model translated the +pages. If you spot a problem in the English while translating, note it for +the next English pass; do not fix it here. diff --git a/.agents/skills/update-content/SKILL.md b/.agents/skills/update-content/SKILL.md new file mode 100644 index 00000000000..4b6935d01fe --- /dev/null +++ b/.agents/skills/update-content/SKILL.md @@ -0,0 +1,48 @@ +--- +name: update-content +description: + Edits English docs pages and opens a PR, working from direct instructions or + from an identify-gaps report. Use when asked to fix, update, or improve the + docs. +disable-model-invocation: true +--- + +# Update content + +Change docs pages and open a PR. + +## What to change + +- **You were given instructions.** Do what they say. +- **You were given a report from `identify-gaps/SKILL.md`.** Make every Must + change. Make Should changes where the right text is clear. Leave Could changes + unless someone asked for them. Anything you did not do goes in the PR + description with a reason. +- **You were only told to improve the docs.** Run `identify-gaps/SKILL.md` + first, then work from its report here. One loop, one PR. + +## How to change it + +- Follow the house style in `AGENTS.md`. +- Keep each edit small. Match the page's voice and structure. Do not rewrite a + page and call it a fix. +- Only write a new page or section if the report or the person asked for it. +- If the docs and the code disagree and you cannot tell which is right, ask. + +## Finish + +Stop when you are done or reach 20 changed files. If work is left, list it in +the PR for the next run. Run Prettier on the files you changed, then build the +site as CI does, which fails on broken links: + +```bash +yarn generate-library +yarn generate-adaptors +yarn build +``` + +Open a PR using the template in `.github/` and tick "I have used Claude Code". +Say what changed, what you left and why, and any questions. If the work came +from a report, paste the report in, collapsed. + +Translations are separate. See `translate/SKILL.md`. diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 00000000000..2b7a412b8fa --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.gitignore b/.gitignore index a35029f4531..d9c0121e742 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,7 @@ .docusaurus .cache-loader -# translation -/i18n +# translation: i18n/ is committed (translations are generated artefacts kept in-repo, see .agents/skills/translate/SKILL.md) # Misc .DS_Store diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..94a9cc02880 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,95 @@ +# Docs maintenance agent + +You look after the OpenFn documentation site. It is a Docusaurus project. Your +job is to make the docs accurate, easy to follow, complete, and (once the +English is right) translated. + +The detailed instructions for each job live in `.agents/skills//SKILL.md` +(`.claude/skills` links to the same folder). Each one stands alone; read the one +you need. + +## What you can and cannot edit + +**Edit freely** + +- Everything in `docs/`. This is the English source of truth. +- `sidebars-main.js`, which controls the navigation. +- The adaptor overview pages in `adaptors/*.md`. + +**Do not edit** + +- Anything in `adaptors/packages/` or `adaptors/library/`. These pages are built + automatically from code comments in the `OpenFn/adaptors` repo. If something + is wrong there, the fix belongs in that repo, not here. +- Anything in `versioned_docs/`. These are the old v1 docs and are frozen. + +**Ask before editing** + +- `docusaurus.config.js`, `package.json`, and anything in `.github/`. These + change how the site builds and deploys. + +**Special rules apply** + +- Translations in `i18n/`. See `translate/SKILL.md`. +- The two rule files: `glossary.yml` and `translation-rules.yml`. Humans + maintain these. Each explains its format at the top. Only add an entry if the + user asks you to. + +To check facts, you can read the product code. Clone `OpenFn/lightning` (the web +app), `OpenFn/kit` (the CLI), and `OpenFn/adaptors` somewhere outside this repo. +Never change them. + +## The skills + +- **`identify-gaps`** reviews the docs and returns a report of recommendations, + each marked Must change, Should change, or Could change. It does not edit + anything. +- **`update-content`** makes changes and opens a PR. It works from instructions, + or from an identify-gaps report. +- **`release-review`** works out what the product shipped recently and passes + that to identify-gaps. Suited to a monthly schedule. +- **`translate`** translates English pages, in its own PR per locale. + +`update-content` and `translate` open PRs, so they only run when someone asks +for them by name (`/update-content`, `/translate`). When another skill hands off +to one of them, read its `SKILL.md` directly. + +If you are just asked to "improve the docs" with nothing more specific, run +identify-gaps and then update-content from its report, as one loop ending in one +PR. + +## Scope + +By default, work across the whole site. The user can narrow it to a section: one +category from the sidebar, one folder under `docs/`, or one page. + +## Rules that never bend + +- Never edit a translated page marked + `translation_review_status: human-reviewed`. Offer a diff instead. +- Never edit generated adaptor pages. Draft an issue for `OpenFn/adaptors` and + put it in the PR. Only file it if asked. +- Never retranslate text inside `` fences. +- Never translate a term listed in `glossary.yml`. +- Never retake, crop, or replace screenshots. +- Never disable a check to make the build pass. + +## When to stop + +Stop at 20 changed files and open a PR (see `update-content/SKILL.md`). +Translations go in their own PR per locale and do not count toward the 20. + +## House style + +- Every page has a `title` in its front matter. +- Internal links start with `/documentation/`, `/adaptors/`, or `/articles/`. + Never use relative `.md` links; they break the build once a page is + translated. +- Images live in `static/img/` and are linked as `/img/filename`, with alt text + that says what the image shows. "Screenshot" does not count. +- Leave a blank line after an admonition's opening line (`:::tip`, `:::note`, + and so on) and before its closing `:::`. Without them, Prettier merges the + text into the opening line and Docusaurus shows it as the title. +- It is spelled **adaptor**, never "adapter". +- Use the approved terms in `glossary.yml`. If a page uses one of the listed + `variants`, replace it. diff --git a/glossary.yml b/glossary.yml new file mode 100644 index 00000000000..59134c92180 --- /dev/null +++ b/glossary.yml @@ -0,0 +1,194 @@ +# glossary.yml +# +# Purpose +# ------- +# Product vocabulary for the OpenFn docs. Two consumers read this file: +# +# 1. The translate skill (.agents/skills/translate/SKILL.md). Any term with +# `translate: false` must appear verbatim in every translated page. +# 2. The house style in AGENTS.md. Any spelling in `variants` is replaced +# with `term` in English pages. +# +# Humans maintain this file. Add a term when a review shows the same +# correction being made more than once. +# +# Schema +# ------ +# terms: # list of glossary entries +# - term: string # canonical English spelling (required) +# translate: boolean # false = keep verbatim in all locales (default false) +# product_noun: boolean # true = the rule only applies when the word is used +# # as the OpenFn concept, not as ordinary English +# # (e.g. "run" the noun, not "run the command"). +# # Translators keep the product noun and may +# # translate the ordinary-English use. Default false. +# case_sensitive: boolean # true = the house-style check flags case variants too (default false) +# variants: [string] # spellings the house-style check flags, to replace with `term` +# note: string # guidance for humans and the agent +# locales: # optional. Only used when translate: true, to pin a +# es: string # specific rendering per locale instead of free +# fr: string # translation. +# +# patterns: # regexes that are never translated, for families +# - pattern: string # of identifiers too numerous to list (adaptor +# note: string # package names, CLI flags, env vars, ...) +# +# Matching is whole-word for `term` and `variants`. Code blocks, inline code, +# URLs, and front matter are always exempt from the house-style check and translation. + +terms: + - term: OpenFn + translate: false + case_sensitive: true + variants: + - Open Fn + - Openfn + - openFn + note: The product and organisation name. Never localised. + + - term: Lightning + translate: false + case_sensitive: true + note: >- + The OpenFn web app (OpenFn/lightning). In user-facing docs prefer + "the OpenFn platform" or "the web app"; keep "Lightning" when the docs + refer to the repo or to self-hosting. + + - term: adaptor + translate: false + variants: + - adapter + - Adapter + note: >- + Always "adaptor", never "adapter". Also covers "adaptors", "Adaptor", + "Adaptors". Adaptor package names are matched by the pattern below. + + - term: workflow + translate: false + product_noun: true + note: >- + A Trigger plus Steps plus Paths configured on the Canvas or in + project.yaml. Keep "workflow" in translations when it names the OpenFn + object. + + - term: step + translate: false + product_noun: true + note: A unit of work inside a workflow. Was "job" in OpenFn v1. + + - term: job + translate: false + product_noun: true + note: >- + In v2 the job is the JavaScript expression a Step runs. Do not + "correct" v1 usage inside pages that are explicitly about v1 or + migration. + + - term: credential + translate: false + product_noun: true + note: Stored authentication configuration attached to a Step. + + - term: trigger + translate: false + product_noun: true + note: What starts a workflow. Types are webhook and cron. + + - term: cron + translate: false + note: Trigger type and the scheduling syntax. + + - term: webhook + translate: false + note: Trigger type. One word, lower case, no hyphen. + + - term: run + translate: false + product_noun: true + note: >- + One execution of a workflow for a work order. Only the noun is + protected. "Run the CLI" is ordinary English and may be translated. + + - term: attempt + translate: false + product_noun: true + note: >- + Legacy name for a run. Do not replace it in migration or historical + pages. In pages about current v2 behaviour, prefer "run" and record the + change as a Should change, not a direct edit. + + - term: work order + translate: false + variants: + - workorder + - work-order + note: The record created when a trigger fires; owns one or more runs. + + - term: project space + translate: false + note: Billing and hosting unit on the hosted OpenFn app. + + - term: project + translate: false + product_noun: true + note: Administrative grouping of workflows, credentials, and collaborators. + + - term: dataclip + translate: false + variants: + - data clip + - data-clip + note: A stored input or output state object. + + - term: collection + translate: false + product_noun: true + note: The Collections key-value store feature. Ordinary English use may be translated. + + - term: sandbox + translate: false + product_noun: true + note: A Lightning sandbox environment. + + - term: Canvas + translate: false + case_sensitive: true + note: The visual workflow editor in the web app. + + - term: Inspector + translate: false + case_sensitive: true + note: The step editing panel in the web app. + + - term: CLI + translate: false + case_sensitive: true + note: "@openfn/cli. Also keep every CLI command and flag verbatim." + + - term: state + translate: false + product_noun: true + note: >- + The `state` object passed between operations. Protected only when it + names the object (usually rendered in code as `state`). + + - term: operation + translate: false + product_noun: true + note: A function exported by an adaptor, e.g. `get()`, `upsert()`. + +patterns: + - pattern: "@openfn/[a-z0-9-]+" + note: npm package names (adaptors, CLI, runtime). + + - pattern: "language-[a-z0-9-]+" + note: Bare adaptor package names as they appear in the adaptor picker. + + - pattern: "\\bopenfn [a-z][a-z-]*" + note: CLI subcommands such as `openfn deploy`, `openfn pull`. + + - pattern: "\\b[A-Z][A-Z0-9_]{2,}\\b" + note: Environment variables and constants (OPENFN_API_KEY, WORKER_SECRET). + + - pattern: "\\bproject\\.yaml\\b" + note: The project state file name. diff --git a/translation-rules.yml b/translation-rules.yml new file mode 100644 index 00000000000..195e8f8c4cd --- /dev/null +++ b/translation-rules.yml @@ -0,0 +1,58 @@ +# translation-rules.yml +# +# Purpose +# ------- +# Locale-specific phrasing rules learned from human edits to machine +# translations. The translate skill (.agents/skills/translate/SKILL.md) loads this +# file after glossary.yml and applies every rule whose `locale` matches the +# target locale. +# +# Glossary terms (never translate) belong in glossary.yml, not here. This file +# is for how to translate, not what to leave alone. +# +# Humans maintain this file. Add a rule when you correct a translation in a +# way that should apply to other pages too. +# +# Schema +# ------ +# rules: +# - locale: string # "es" or "fr" (or "*" for every locale) +# kind: string # one of: +# # term - a fixed rendering for a phrase +# # register - tone/voice guidance (formal vs informal "you") +# # punctuation - spacing, quotation marks, list punctuation +# # structure - how to handle headings, admonition titles, UI labels +# # avoid - a rendering the reviewer rejected +# source: string # English phrase or pattern the rule applies to (for +# # kind: term and avoid). Omit for global rules. +# target: string # required rendering (kind: term) or rejected rendering +# # (kind: avoid) +# instruction: string # plain-language rule the translator must follow +# example_source: string # optional English example +# example_target: string # optional translated example +# reason: string # why, in one sentence +# added_by: string # GitHub handle +# added_on: string # ISO date (YYYY-MM-DD) +# source_pr: string # PR URL or number where the edit happened +# +# Example +# ------- +# rules: +# - locale: es +# kind: register +# instruction: Address the reader as "tú", not "usted". +# reason: Matches the informal tone of the English docs. +# added_by: someone +# added_on: 2026-01-01 +# source_pr: https://github.com/OpenFn/docs/pull/000 +# - locale: fr +# kind: punctuation +# instruction: Put a non-breaking space before ":", ";", "?" and "!". +# example_source: "Next step: add a credential." +# example_target: "Étape suivante : ajoutez un identifiant." +# reason: Standard French typography. +# added_by: someone +# added_on: 2026-01-01 +# source_pr: https://github.com/OpenFn/docs/pull/000 + +rules: []