Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

OpenInterviewer is an open-source platform for adaptive, AI-assisted qualitative interviews. Researchers configure a study, share an opaque participant link, and review transcripts and synthesis in a dashboard. An AI interviewer conducts each interview, and the transcript is saved before any analysis runs.

The latest release is 5.4.0, which adds on-device voice input and analysis files in the study export. Read the [5.4.0 release notes](docs/releases/v5.4.0.md) or [all release notes](docs/releases/).
The latest release is 5.4.1, which fixes how Chinese and Japanese dashes display. Version 5.4.0 added on-device voice input and analysis files in the study export. Read the [5.4.1](docs/releases/v5.4.1.md) and [5.4.0](docs/releases/v5.4.0.md) release notes, or [all release notes](docs/releases/).

The project's own instance is [open-interview.linxule.com](https://open-interview.linxule.com). It is a standalone installation on Cloudflare, with an EU-jurisdiction workspace and provider calls through its own Cloudflare AI Gateway. The former address, `openinterviewer.vercel.app`, redirects there.

Expand Down Expand Up @@ -133,7 +133,7 @@ For contributors:

- [Development and verification](docs/development.md): checks, test lanes, live-provider smoke test, releases, provider API and model contract, and project structure
- [Contributing guide](CONTRIBUTING.md) and [agent and architecture guide](AGENTS.md)
- [Release notes](docs/releases/), latest [5.4.0](docs/releases/v5.4.0.md)
- [Release notes](docs/releases/), latest [5.4.1](docs/releases/v5.4.1.md)
- [Translation review, October 2026](docs/translations/REVIEW-2026-10.md)

## Acknowledgments
Expand Down
23 changes: 23 additions & 0 deletions docs/releases/v5.4.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenInterviewer 5.4.1

Chinese and Japanese dashes and ellipses now display correctly. Before this fix,
"——" in a Chinese sentence or button, such as 我同意——开始访谈, showed as two
short, separated dashes, and "……" showed as Latin dots.

## What changed

- On Chinese and Japanese participant pages, the dash (U+2014, U+2015) and the
ellipsis (U+2026) are drawn from a CJK font already installed on the device,
such as PingFang, Hiragino, Microsoft YaHei or Noto Sans CJK. All other
characters keep the site's fonts, and nothing new is downloaded.
- The fix covers fixed screens, the researcher's consent text and the AI
interviewer's replies.
- In the researcher's transcript view, each turn is marked with the interview
language. The same dashes display correctly there, and screen readers read the
turn in the right language.
- English and the other interface languages are unchanged.

## Upgrade and rollback

A code-only release, with no schema, configuration or binding changes. Rolling
back to 5.4.0 is safe.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openinterviewer",
"version": "5.4.0",
"version": "5.4.1",
"description": "Open-source platform for adaptive, AI-assisted qualitative interviews.",
"private": true,
"license": "MIT",
Expand Down
34 changes: 34 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,40 @@
--shadow-note: var(--note-shadow);
}

/*
CJK dashes and ellipses. In Chinese and Japanese text the Latin web fonts
supply U+2014/U+2015 and U+2026, so "——" draws as two short, separated
dashes and "……" as Latin dots. On zh/ja content, take only those code points
from the system CJK font (unicode-range), so they join as the reader expects.
Applies to participant pages (html lang is set from the interview language)
and to any element marked with lang="zh-…" or lang="ja".
*/
@font-face {
font-family: 'OI CJK Punctuation SC';
src: local('PingFang SC'), local('PingFangSC-Regular'), local('Hiragino Sans GB'), local('Microsoft YaHei'),
local('Noto Sans CJK SC'), local('NotoSansCJKsc-Regular'), local('Source Han Sans SC'), local('Noto Sans SC');
unicode-range: U+2014-2015, U+2026;
}

@font-face {
font-family: 'OI CJK Punctuation JP';
src: local('Hiragino Sans'), local('HiraginoSans-W3'), local('Hiragino Kaku Gothic ProN'), local('Yu Gothic'),
local('Meiryo'), local('Noto Sans CJK JP'), local('NotoSansCJKjp-Regular'), local('Source Han Sans JP');
unicode-range: U+2014-2015, U+2026;
}

:lang(zh) {
--font-sans: 'OI CJK Punctuation SC', var(--typeface-sans), system-ui, sans-serif;
--font-serif: 'OI CJK Punctuation SC', var(--typeface-serif), Georgia, serif;
--font-mono: 'OI CJK Punctuation SC', var(--typeface-mono), ui-monospace, monospace;
}

:lang(ja) {
--font-sans: 'OI CJK Punctuation JP', var(--typeface-sans), system-ui, sans-serif;
--font-serif: 'OI CJK Punctuation JP', var(--typeface-serif), Georgia, serif;
--font-mono: 'OI CJK Punctuation JP', var(--typeface-mono), ui-monospace, monospace;
}

/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
Expand Down
3 changes: 2 additions & 1 deletion src/components/InterviewDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { InterviewAnalysisPanel } from '@/components/analysis/InterviewAnalysisP
import { useInterviewAnalysis } from '@/components/analysis/useInterviewAnalysis';
import { useSetTrailingCrumb } from '@/components/shell/breadcrumb';
import { cn } from '@/lib/cn';
import { LANGUAGE_TAGS } from '@/lib/i18n/languages';

interface InterviewDetailProps {
interviewId: string;
Expand Down Expand Up @@ -297,7 +298,7 @@ const InterviewDetail: React.FC<InterviewDetailProps> = ({ interviewId, studyId,
showCoordinate
className="mt-1"
>
<div className="prose-verbatim">
<div className="prose-verbatim" lang={interview.interviewLanguage ? LANGUAGE_TAGS[interview.interviewLanguage] : undefined}>
<ReactMarkdown>{msg.content}</ReactMarkdown>
</div>
</Turn>
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/selfHostedFonts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,17 @@ describe('self-hosted fonts', () => {
expect(latin).toContain(`{ prop: 'font-family', value: "'${family}'" }`);
}
});
it('draws CJK dashes and ellipses from a local CJK font on zh and ja content only', () => {
const theme = readFileSync(path.join(process.cwd(), 'src/app/globals.css'), 'utf8');
for (const [selector, face] of [[':lang(zh)', 'OI CJK Punctuation SC'], [':lang(ja)', 'OI CJK Punctuation JP']]) {
const block = theme.slice(theme.indexOf(`${selector} {`), theme.indexOf('}', theme.indexOf(`${selector} {`)));
for (const variable of ['sans', 'serif', 'mono']) {
expect(block).toContain(`--font-${variable}: '${face}', var(--typeface-${variable}),`);
}
const faceRule = theme.slice(theme.indexOf(`font-family: '${face}';`));
// Only the dash and ellipsis code points, and only fonts already on the device (no download).
expect(faceRule.slice(0, faceRule.indexOf('}'))).toContain('unicode-range: U+2014-2015, U+2026;');
expect(faceRule.slice(0, faceRule.indexOf('}'))).not.toContain('url(');
}
});
});
Loading