Release notes

CycleWingman 1.4: Russian and Polish

September 10, 2026 · UpFlyDev engineering

cyclewingmanreleaseengineering

CycleWingman now speaks three languages. That sentence took a fortnight, and almost none of it was translation — it was unpicking the places where English had quietly become part of the machinery.

What's new

  • Russian and Polish, everywhere: the daily forecast, the journal and its patterns, the planner, your heads-up notifications, the Apple Watch app and every widget.
  • The language follows your phone by default, and can be pinned in Settings if you would rather read the app in something else. The app now declares the languages it ships, so iOS and Android show it in their own per-app language settings too.
  • Longer words stopped breaking mid-word on the watch and in the square widget — slots cut for “Orange” do not fit «Оранжевый».
  • The planner says “45 days” where it used to say “45”. A small bug that had been sitting in plain sight in English.

Nothing about the privacy model changed. There is still no account, no server and no analytics; adding two languages added no network calls, because the dictionaries ship inside the app.

Under the hood

The engine had to stop returning sentences

The forecast engine returned finished English: “Care mode: activated.” That looked harmless until we tried to translate it, and found the same string was doing three unrelated jobs at once.

It was the parity contract — the engine exists twice, in TypeScript and in a Swift mirror that drives the watch and the widgets, and golden-fixture tests pinned them together by comparing that prose. It was the storage key for the per-phase calibration written on each partner's profile. And it was glued into sentences in the journal, where “hard days cluster in the follicular phase” is assembled from a fragment.

Translating any one of those would have broken the other two. So the engine stopped returning words at all: it now returns a phase identifier and a line index, and a single presentation layer turns those into language. The parity tests got better as a side effect — they now compare arithmetic instead of copy, which is what they were always meant to check.

Functions, not templates with holes in them

The usual way to localise is a string with a placeholder: "{n} days". That works in English and quietly imposes English on everything else. Russian needs three plural forms; Polish needs three of its own, and a phase name has to change ending depending on the sentence around it.

So entries that vary with a number or a phase are functions, and each language writes its own. That is why the app can say «через 1 день», «через 2 дня» and «через 5 дней» without a special case in the view.

The harder version of the same problem is the partner's name, which is free text you typed. Russian and Polish would want to decline it — “Has Anna's cycle started?” needs a genitive no program can derive from an arbitrary name. Every sentence that touches the name is now built so the name can stay exactly as written.

A typeface with no Cyrillic in the weight we use

Russian looked thinner than English and Polish, and it was not a styling choice. Avenir Next carries Cyrillic in Regular through Bold — but not in Heavy, and nearly every heading in the app is Heavy. So each Russian heading silently fell out of the family into whatever the system offered, while the Latin beside it stayed put.

We only found it by reading the font's own character table. The fix maps the Cyrillic range onto the heaviest cut the family actually has, so Russian stays in the same typeface as everything around it.

Reading the copy as copy

The strings that matter most are the ones a function builds, and those are invisible in a diff: the source says plural(days, ...), not what a reader will see. So the app grew a small internal tool that renders every user-visible string in all three languages side by side, with sample numbers chosen so plural rules are actually reviewable — 1, 2 and 5 on screen at once, which is exactly where Russian and Polish stop agreeing with English.

It found subject-verb disagreement in three languages within a minute of existing, including in the English we had been shipping for months.

Get CycleWingman — free, sets up in under a minute, and nothing ever leaves your phone. On both stores.

Download on the App Store Get it on Google Play

Learn more about CycleWingman →