Introduction
Being able to write an article isn't enough to make day-to-day operation work. Requests like "I want to save this half-finished," "I want it live the moment the campaign starts," and "we need an English version too" always show up.
This article covers the operational details around publishing. It's unglamorous territory, but if this part is awkward to use, the operators simply stop updating the content.
Managing articles with three states
Draft, published, unpublished
Articles have three states: draft, published, and unpublished.
We keep draft and unpublished separate because they mean completely different things. A draft is "not out yet"; unpublished is "it was out, and we pulled it back." Being able to tell them apart in the dashboard list makes the situation much easier to read at a glance.
Scheduled publishing without adding a state
One option would have been a fourth state called "scheduled." Instead, we expressed it as a combination: published state plus a future publish date.
Publish date in the future means scheduled; in the past means live. That's all it takes. Adding a state multiplies the possible transitions and the complexity along with them, so if an existing field can express the idea, that's the safer route.
In the dashboard list, articles that are in the published state with a future date get a "Scheduled" badge, so it's obvious to the operator what's going on.
No more staying up for a campaign launch
Set it to "go live at midnight tomorrow" and nobody has to be awake to press a publish button. It's an unassuming feature with an outsized effect on day-to-day workload.
Dates and times are handled in Japan time
The date-and-time input uses the browser's built-in control, which by default follows the clock settings on the user's computer. Being nine hours off because someone is working from abroad is not acceptable.
So any entered date and time is always interpreted as Japan time, and converted back to Japan time when displayed. When the operator types "8:00 PM," they mean 8:00 PM in Japan. Obvious, perhaps — but it falls apart if you don't specify it explicitly.
Making four languages workable
Editing four languages on one screen
Articles can be held in four languages: Japanese, English, Traditional Chinese, and Korean. The editing screen splits them across tabs, but there's only one save button.
Switching tabs preserves what you've typed, and saving registers all four languages together. Per-language save buttons make it far too easy to have the "I forgot to save the English one" accident.
Only the Japanese title is required. Conversely, if a language's title, summary, and body are all empty, that language's version of the article is deleted. So when you decide "actually, let's not publish the English version," clearing the fields is all it takes.
AI translation only runs when you press the button
The heaviest burden in going multilingual is translation. So we put a "Generate AI translation" button in the dashboard that produces the other three languages from the Japanese article.
Get the Japanese version finished first
It never runs automatically on save; the operator asks for it explicitly
English, Traditional Chinese, and Korean are generated in parallel. If one fails, the others still complete
The generated text is editable. If something reads wrong, fix it before publishing
There are two reasons we don't translate automatically on every save. One is cost: if translation ran on every save, API charges would pile up fast. The other is that hand-corrected translations would get overwritten by the next save.
Translating without breaking the formatting
The crucial thing during translation is not to break the article's HTML structure. Headings should stay headings, and shop cards should come through with their identifiers intact.
So the instructions we give the AI explicitly require that the HTML structure be preserved, and the generated result goes through exactly the same sanitization as any other article before it's saved. The policy is simple: being AI output is not a reason to skip inspection.
For the titles and descriptions that appear in search results, we use a separate instruction asking for phrasing that reads naturally in the target language rather than a literal translation.
Untranslated languages fall back to Japanese
If someone arrives in English at an article that doesn't have an English version yet, we show the Japanese version rather than saying the article doesn't exist. Serving something readable felt kinder than serving an empty page.
Publishing shows up immediately
Caching and instant updates, together
To keep the site fast, page content is cached for a set period. But from the perspective of an operator who just hit publish, "I published it and it's not on the site" is unnerving.
So when an article is saved or published, we explicitly invalidate the cache for the related pages. The listing page, the article page, the homepage, the sitemap — all of them update immediately.
Cache duration stays, for the sake of scheduled publishing
With instant updates in place, the cache duration might look unnecessary — but scheduled publishing needs it. An article has to go live once its set time passes even though nobody is editing anything. So we run two tracks: manual updates trigger instant refresh, and the passage of time triggers automatic refresh.
Pages stay up even if the database goes down
If the database becomes unreachable, does the blog listing page just show an error? That won't do.
We built in a fallback: if articles can't be fetched from the database, prepared sample articles are shown instead, along with a note saying "this is a sample view." It's a far better state to be in than the whole site going down.
Wrapping up
Three things mattered most in designing the publishing flow.
- Don't add states — scheduled publishing is expressed as "published state plus a future date," avoiding extra complexity
- AI translation runs explicitly — never automatically, which keeps cost down and protects hand-edited text
- Two tracks: instant and time-based — manual updates invalidate the cache, scheduled posts go live with the clock
Site Structure That Leads Straight to Booking
Where published content sits within the site as a whole.
A Rich-Text Editor with Safe HTML — Double Sanitization Against XSS
How article safety is handled, AI translation output included.
Blog and News Run In-House — a Self-Built CMS for the Rental Site
For the design of the dashboard as a whole, see this hub article.