diff --git a/app/src/Book.jsx b/app/src/Book.jsx index 605d2bd..d651e7a 100644 --- a/app/src/Book.jsx +++ b/app/src/Book.jsx @@ -251,7 +251,9 @@ function Clippings({ session, book, revision = 0 }) { ) } -const norm = (s) => (s ?? '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim() +// Letters, marks and digits of every script: an ASCII-only pattern reduces Japanese titles to their +// digits, and dropping marks merges Thai or Hindi words that differ only in a tone or vowel sign. +const norm = (s) => (s ?? '').normalize('NFKC').toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, ' ').trim() // Same normalized title (and author when both have one): probably the same book synced twice. export const looksLikeSame = (a, b) => a.document !== b.document && norm(a.title) && norm(a.title) === norm(b.title) && (!a.author || !b.author || norm(a.author) === norm(b.author)) diff --git a/app/src/Library.test.jsx b/app/src/Library.test.jsx index df60d78..7c36f14 100644 --- a/app/src/Library.test.jsx +++ b/app/src/Library.test.jsx @@ -46,3 +46,28 @@ describe('Library completion updates', () => { expect(queryByText('Continue reading')).toBeNull() }) }) + +describe('duplicate notice', () => { + const book = (document, title) => ({ document, title, author: 'Unknown Author', status: 'finished', percentage: 1 }) + + it('tells different Japanese titles apart', () => { + const books = [ + book('grammar', '小学1年生 言葉と文法にぐーんと強くなる (くもんの国語集中学習)'), + book('kanji', '小学1年生 漢字にぐーんと強くなる (くもんの国語集中学習)'), + ] + const { container } = render() + expect(container.textContent).not.toContain('appears more than once') + }) + + it('tells Thai titles apart that differ only in a tone mark', () => { + const books = [book('forest', 'ป่า'), book('aunt', 'ป้า')] + const { container } = render() + expect(container.textContent).not.toContain('appears more than once') + }) + + it('still finds a Japanese title synced twice, full-width forms included', () => { + const books = [book('a', '変な家2 ~11の間取り図~'), book('b', '変な家2 ~11の間取り図~')] + const { container } = render() + expect(container.textContent).toContain('appears more than once') + }) +})