Skip to content
Lazy Engineer's Tech Blog
Go back

Translating an Obsidian plugin to English, I found button text doing the job of state values

한국어로 읽기 →

Building an LLM Wiki in Obsidian · Part 6 of 6

I’m building an Obsidian plugin for my own use. It isn’t public yet: you feed it articles or memos, and an LLM organizes them into per-concept notes that build up a wiki. This week I did two things. I renamed the plugin, and I made its screens, which only showed Korean, show English too. Both looked like find-and-replace jobs, but once I opened the code, some of the strings I meant to change were doing something other than being a name or a label. I worked with an AI coding agent, and most of the work was finding those places before translating or replacing anything.

Renaming one thing, and no bulk replace (renaming an Obsidian plugin)

The old name was Gemini Butler. It started out Gemini-only, but OpenAI is now the default and Gemini is one of three providers to choose from. Only the name still said Gemini, so I renamed it to Ripen Wiki. But I couldn’t delete every gemini in the code.

So I didn’t do a bulk replace. I listed the classes to change, changed only those, and then counted again to check that the remaining gemini- strings were all model names.

The plugin id was trickier. Obsidian keeps a plugin’s data in a folder named after its id. A new id is a new plugin as far as Obsidian is concerned, so settings, API keys and the embedding cache don’t come along. I could have written migration code, but only two vaults use this plugin, so it would be code that runs twice and never again. I chose to copy the files by hand once per vault.

I install it with BRAT, which installs beta plugins from a GitHub repository. Updating the existing entry failed with “Update of plugin failed”. I never found out why. I didn’t create a new repository; I only renamed it (issues and past releases carry over), and adding the new address again with “Add Beta plugin” in BRAT gave a clean install in a new folder. I then copied the settings file from the old folder into it.

Building an English UI meant sorting Korean strings into three kinds (Obsidian plugin i18n)

I had already switched the settings screen to English. What was left was the chat window. The chat window and command result code had no calls to a translation function at all, and about 400 lines contained Korean. I sorted them into three kinds.

KindExamplesHandling
UI textinput placeholder, buttons, progress, command result reports, notices, errorstranslate
Text sent to the LLMprompts, note lists passed to the LLMleave as is: these prompts were tuned by measurement
Text written into the vaultthe activity log file, note format guidanceleave as is: note language must not get mixed every time the display language changes

”Won’t doing it all at once mix things up?”: UI text doubling as state (data attributes)

The agent asked whether to do the three areas at once or one at a time, and I asked whether doing them all at once wouldn’t mix things up. The agent split that into two answers. Korean and English showing up mixed on screen could be prevented by releasing only once at the end. The real risk was a string that was UI text and also used for something else. If the code checks UI text as a condition, the moment that text is translated, the feature stops on the English screen without any error. So before starting the translation, we looked for strings like that first, and found some.

The fix was the same each time: keep state separate. Button state lives in a data-run-state attribute (idle, running, done), whether a card is auto-detected lives in a flag passed when the card is created, and decisions use only those. The screen text comes from the dictionary. The completion summary values became done/missing and are translated when shown. I added separate tests that the button state checks and the row split still hold in English mode.

The LLM’s Korean was still on the English screen (LLM output language)

With the translation done, I ran the commands in English in a test vault. All the UI text came out in English. But the merge reasons in the tag cleanup results were in Korean. The LLM writes those reasons, and since the prompt is in Korean, the LLM answers in Korean. Prompts were outside the agreed scope, so the agent asked whether to leave it. I said that if you tell someone who reads English something in Korean, without a Korean font it would come out looking wrong.

So only the LLM-written text that shows up on screen as-is follows the display language: tag merge reasons, the reasons on note cleanup suggestions, auto-merge reasons, and the list of referenced notes at the end of chat answers. The language rule for note bodies (follow the source’s language) stayed as it was.

There was one condition: the Korean-mode prompts must not change by a single character, so results tuned by measurement don’t shift. Only English mode changes or adds instructions, and I compared the five Korean prompts before and after to confirm they were identical.

Another dual-use string showed up here. The chat window removes the referenced-notes display when an answer contains the sentence “인용된 보관함 지식이 없습니다.” (no vault knowledge was cited). Once the LLM starts writing in English, that sentence no longer appears. I made the sentence in both languages constants, and the display is now removed when either one is present.

Running it in English mode, the tag merge reasons and the referenced-notes list in chat answers came out in English. The note cleanup suggestions produced no suggestions in that scan, so I haven’t yet seen on screen whether their reasons come out in English. I only confirmed with unit tests that the instruction gets in.

After the release, one miss came to light. Auto-merge reasons are stored along with the verdict and reused in the next scan, but the language they were written in wasn’t recorded, so a reason received in Korean mode showed up on the English screen as it was. In the next release I made it show that language’s generic text instead of the stored reason when the languages differ.

Closing thoughts

Renaming and i18n look like different kinds of work, but they snagged on the same thing. A string that looked like a name was a model id, and a string that looked like UI text was a state value. A bulk change would have stopped some feature only on the English screen, without a single error message. Before changing a string, look for code that compares against it, and don’t judge state by on-screen text in the first place. What got me to look this time was the short question “won’t that mix things up?”


Share this post:

Building an LLM Wiki in Obsidian

  1. 1. Obsidian에 LLM Wiki 만들기 — 시리즈 소개 (Korean only)
  2. 2. Obsidian에 LLM Wiki 만들기 1편 — 이미 있는 플러그인 10개를 두고 왜 또 만들었나 (Korean only)
  3. 3. Obsidian에 LLM Wiki 만들기 2편 — 자정이 지나도 날짜가 안 바뀌던 버그 (Korean only)
  4. 4. Obsidian에 LLM Wiki 만들기 3편 — 동적 뱃지와 dirty check, UX가 엔지니어링을 만날 때 (Korean only)
  5. 5. Obsidian에 LLM Wiki 만들기 4편 — 자율 Ingest 엔진으로 진화하기까지 (Korean only)
  6. 6. Translating an Obsidian plugin to English, I found button text doing the job of state values