Stationnement, Not Parking: Building a Small Quebec French Scanner
Why sounding local matters on Canadian French websites. My experiment combining simple NLP, OQLF data and an LLM to find French gaps, and how it broke along the way.

TL;DR People in Canada and people in France both speak French, but they use some different words. In Quebec you park in a stationnement. In France you park in a parking. A website with a Canadian French version feels more at home when it uses the local words. I built a small tool that reads a website and points out words that sound like France, plus English that someone forgot to translate. Simple rules do most of the work. An AI helps with the tricky cases. A person makes the final call. A lot of things broke along the way.
I knew Quebec French was different from France French long before I lived here. I had worked in localization for years in Asia and Europe, and “fr-FR vs fr-CA” was a line item in most project plans. The knowledge was there, and the experience became real after I moved to Montreal in 2019. The sign at the mall says stationnement, not parking. A weekend is a fin de semaine. You don’t faire du shopping, you go magasiner.
None of these is wrong French. A reader in Montreal understands parking perfectly well. But it reads like a site written somewhere else.
Why sounding local matters
Quebec has its own linguistic identity, and the province takes French seriously. Bill 96 put more weight on French in business, and that is part of the background here. But this post is not about the law. The law asks for French. It does not tell a business which French words to use.
The question I was curious about is a market one. If a company builds a Canadian French version of its site, it already decided local readers matter. At that point, sounding local is the cheap last step. Stationnement instead of parking. Courriel instead of mail. A French button where an English one slipped through.
The hard part is finding those spots. A website has hundreds of pages and thousands of strings. Some only show up on an error, inside a collapsed FAQ or in image alt text that only a screen reader reads. Reproducing every path, happy and sad, to check the French by hand is slow, tedious work. It is not the best use of a human reviewer’s time.
So I built a small tool to do the finding, and leave the deciding to people.
What the scanner does
The Quebec French Website Scanner takes a URL, crawls the French pages and reports three main kinds of gaps.
French coverage gap. An English page with no French version the crawler can find.
English text on a French page. Buttons, menus, error messages, page titles, alt text. The scanner also checks that each French page is tagged as French.
Quebec French term. A word or phrase that is fine in France but has a usual Quebec equivalent. When a record exists, the finding links to it in the OQLF’s Grand dictionnaire terminologique (GDT), so the suggestion comes with a source, not my opinion. A term with no record goes to review.
Every result is a candidate. The report explains it, shows the sentence around it and lets a reviewer mark it correct, wrong or unsure. The site owner makes an informed call. The scanner does not make it for them.

Part of a report on Northwind, a fictional coffee shop site I built to test the scanner.
Why not just send everything to an LLM?
The obvious design is to paste each page into a large language model and ask it to fix the French. I tested that, and it was the worst option I tried.
A website has a lot of text, and most of it repeats. The same menu, footer and cookie banner appear on every page. Sending all of it to a model costs tokens on text that has nothing to say. Worse, a model reading a whole page decides on its own what to change. Cohere’s Command A “corrected” Quebec French into France French. It suggested changing déjeuner to petit-déjeuner. In Quebec, déjeuner is breakfast. In France, it is lunch, and breakfast is petit-déjeuner. The prompt asked for Quebec French, but the model still reached for France. That is the exact problem the tool exists to find.
How it fits together
So I split the work into layers. Each one does the part it does well and at low cost. The numbers match the boxes in the diagram.
1. NLP checks. A language detector (Lingua) plus word frequency data (wordfreq) finds leftover English. Short strings needed both: Lingua alone rates “Contact us” only 54% English. A curated term list finds the France French and anglicism candidates. This layer costs nothing to run and handles most findings on its own.
2. Merge repeats. A footer that appears on 50 pages becomes one finding, with the 50 pages listed under it. A reviewer reads it once, and only one copy goes to the model.
3. Grounding. Some matches are ambiguous. For those, the scanner pulls the matching OQLF record and sends it with the sentence. It is a simple lookup, not a search engine. But it means the model judges against the record, not against what it half remembers about the norm.
4. LLM second pass. The model gets one sentence, one term and one record, and answers a narrow question: is this a real gap here? It is good at that. It is bad at open ended review.
5. Human review. Neither the rules nor the model is perfect, so a person makes the final call. In my tests, sending the sentence around an item fixed two false flags. It also made the model approve English alt text on a French image, because the image showed a French logo. So when a model says English alt text is fine, the item still goes to a person.
The dotted line is the part I have not built yet. If this approach were used for real, reviewer verdicts would need to flow back into the term list, and into the records the model sees. Without that loop, the same false alarm comes back every scan.
The layers also keep the cost down. On a 50 page scan of a real Quebec site, the second pass used about 2,600 tokens. Sending every page to the same model as is would have used an estimated 830,000. That is roughly 300 times more. I measured the visible text on each page and applied the token rate from my pilot.
Does the layered approach work?
In a small pilot, yes. I built a fictional neighbourhood guide with 26 planted cases of four tricky words: mail, chat, booking and spam. Half were anglicisms, like écrivez-nous par mail. Half were correct, like a mall, a cat in a bookshop window, Booking.com and a can of Spam.
Each case has one right answer: flag it or leave it alone. I counted a mistake every time a tool got that answer wrong. I also counted a mistake for every change a tool suggested to text that was already fine.
The rules alone flagged all 26. That caught the 13 real anglicisms, but it also flagged the 13 correct uses, every mall and every cat. Command A reading the full pages flagged malls and Booking.com six times and missed one real anglicism. It also suggested 11 or 12 changes to text that was already fine. The rules plus Command A as a second pass got all 26 right in all 3 runs.
On these 26 planted cases, here is how the three approaches compared.
| Approach | Mistakes per run | Tokens used |
|---|---|---|
| Rules alone | 13 | none |
| Rules + LLM (Command A) second pass | 0 | 41% of the full page scan |
| LLM (Command A) reads full pages | about 18 (average of 3 runs) | the full amount |
Real pages are much longer than my fixture, so the token gap would grow.
I also tried two small models: Qwen 2.5 7B and Cohere’s Command R7B. Both made more mistakes in either setup, but the second pass still beat the full page scan with all three models. A narrow task helps a model stay on track. It does not make a weak model reliable. The good news is that the rules keep the volume small, so a strong model stays affordable.
In this pilot the model got the sentence and the term, not the OQLF record. I added the record later and have only tried it on 8 real items so far. On one of those items, it made the difference. Without the record, the model called date d’expiration on a credit card page an anglicism and said the OQLF recommends date de péremption, a food term. With the record, two models got it right and cited it.
This is a pilot, not a benchmark. I wrote the cases and the prompts myself. The real test is findings from real Quebec sites, labelled by hand.
Things that went wrong
Building this was less straightforward than I expected. A partial list.
Hydro-Québec. My first real scan reported 50 English pages with no French version. The French pages existed. My crawler did not ask for French, so the French start page sent it to English. It spent its page budget there and never came back. My takeaway: a finding is only as good as the crawl behind it. The scanner now asks for Quebec French, visits French pages first and no longer counts a French page as missing just because the crawl did not reach it.
Mail Champlain. mail is on the list as an anglicism for email. But what about Mail Champlain (Champlain Mall)? In Quebec, a mail is also a shopping mall. The GDT lists mail as a preferred term for galerie marchande, the covered walkway lined with stores. A word with a correct local meaning cannot be an automatic flag. I moved mail to needs review. A real anglicism like envoyez-nous un mail now goes to review too. I accepted that cost, and it is the reason I tested an LLM second pass.
The confident model. Cohere’s larger Command A Plus told me the OQLF recommends centre commercial over mail. The OQLF’s galerie marchande record lists mail as a preferred term. I stopped trusting what a model remembers about the norm. That is why term items now travel with their OQLF record.
The literal model. My prompt showed a sample answer, with “at most 15 words” where the reason should go. A small model copied that phrase as its reason for every item. Technically, it followed instructions. It also got many decisions backwards, so I ruled it out.
The capitalization model. Another small model flagged every capitalized brand or name, including Booking.com and Spam. It also decided “ouvrez le chat” was about a cat. Neither small model made the cut.
“1 an”. The scanner flagged “1 an” (one year) as English. Two letters are not much to judge a language by. I now skip known French units like an, h and km, and send other short words like “5 pm” to review instead of guessing.
Sépaq. The site blocked the scanner on the first request. Fair enough. I decided not to disguise the scanner as a browser. A real product would need the site owner’s permission. For testing, I added a way to scan pages saved from a browser.
My own reports. My second scan quietly overwrote the first one. That was my baseline for comparing later fixes, so I treated it as the top priority bug. Reports now get a timestamp and never replace an old file.
Try it yourself
The code is on GitHub. It is a personal, noncommercial project. It gives suggestions, not legal advice. Feel free to fork it. The code is under the MIT license. The OQLF data is not stored in the repository. A script downloads it from Données Québec. The data is under CC BY-NC-SA 4.0, which restricts commercial use without the OQLF’s permission. It runs on your own computer with Python. The README covers setup and your first scan.
To give it a spin, start with the term list. If you know a web term Quebec sites keep getting wrong but it’s not there, add it or tell me. Then run the scanner on a site you own and tell me what it got wrong. False alarms are the most useful feedback to get.
The scanner will not make a site sound like Montreal. It can show where it doesn’t yet.
Terminology data comes from the OQLF’s public linguistic datasets on Données Québec, used under CC BY-NC-SA 4.0.