Skip to content

Commit

Permalink
79 add a word overview page (#125)
Browse files Browse the repository at this point in the history
* Initial version of word page

* Add word page and syllable breakdown
Add vitest to help with logic

* simplify word page

* Update words page
  • Loading branch information
byronwall committed Oct 29, 2023
1 parent b93b64b commit 584ad44
Show file tree
Hide file tree
Showing 10 changed files with 7,570 additions and 8,456 deletions.
11 changes: 0 additions & 11 deletions babel.config.js

This file was deleted.

15,513 changes: 7,071 additions & 8,442 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "next dev",
"postinstall": "prisma generate",
"lint": "next lint",
"start": "next start"
"start": "next start",
"test": "vitest"
},
"dependencies": {
"@dword-design/eslint-plugin-import-alias": "^4.0.8",
Expand Down Expand Up @@ -37,6 +38,7 @@
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmu-pronouncing-dictionary": "^3.0.0",
"date-fns": "^2.30.0",
"lucide-react": "^0.269.0",
"next": "13.5.2",
Expand All @@ -55,7 +57,6 @@
"zustand": "^4.4.1"
},
"devDependencies": {
"@locator/babel-jsx": "^0.4.2",
"@types/eslint": "^8.37.0",
"@types/node": "^18.16.0",
"@types/prettier": "^2.7.2",
Expand All @@ -73,7 +74,8 @@
"prettier-plugin-tailwindcss": "^0.2.8",
"prisma": "^5.1.1",
"tailwindcss": "^3.3.3",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"vitest": "^0.34.6"
},
"ct3aMetadata": {
"initVersion": "7.18.0"
Expand Down
39 changes: 39 additions & 0 deletions src/app/words/[word]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { useParams } from "next/navigation";

import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { getSyllables } from "~/lib/splitArpabet";

export default function WordPage() {
const params = useParams();

const word = params?.word ?? "";

if (typeof word !== "string") {
throw new Error("Word is not a string");
}

const { syllables, stressLevels, arpabet } = getSyllables(word);

return (
<div className="container mx-auto max-w-2xl">
<Card className="w-96">
<CardHeader>
<CardTitle>{word}</CardTitle>
</CardHeader>
<CardContent>
<p>
<strong>Arpabet:</strong> {arpabet}
</p>
<p>
<strong>Syllables:</strong> {syllables.join(" · ")}
</p>
<p>
<strong>Stress levels:</strong> {stressLevels.join(" · ")}
</p>
</CardContent>
</Card>
</div>
);
}
25 changes: 25 additions & 0 deletions src/app/words/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import Link from "next/link";

import { trpc } from "~/lib/trpc/client";

export default function Page() {
const { data: allWords } = trpc.wordRouter.getAllWords.useQuery();

return (
<div>
<h1>Words</h1>
<div className="flex max-w-3xl flex-wrap gap-3">
{allWords?.map((word) => (
<div
key={word.id}
className="cursor-pointer rounded-md bg-gray-100 p-2 shadow transition-colors duration-200 ease-in-out hover:bg-gray-200 hover:shadow-lg"
>
<Link href={`/words/${word.word}`}>{word.word}</Link>
</div>
))}
</div>
</div>
);
}
4 changes: 4 additions & 0 deletions src/config/marketing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@ export const marketingConfig: MarketingConfig = {
title: "Awards",
href: "/awards",
},
{
title: "Words",
href: "/words",
},
],
};
125 changes: 125 additions & 0 deletions src/lib/splitArpabet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { dictionary } from "cmu-pronouncing-dictionary";

export type SyllableInfo = {
syllables: string[];
stressLevels: number[];
arpabet: string;
};

export function getArpabet(word: string): string | undefined {
const arpabetCode = dictionary[word];

return arpabetCode;
}

export function getSyllables(word: string): SyllableInfo {
const arpabetCode = getArpabet(word);

if (arpabetCode === undefined) {
throw new Error(`Word not found: ${word}`);
}

return splitArpabet(arpabetCode);
}

// rules that appears to be true for splitting into syllables
// all syllable parts must be returned -- don't drop any
// only 1 number = return everything as a single syllable "D AO1 G"

// maybe a 1 tells you to split the syllable before it
// "JH ER1 N IY0" = "JH ER1 | N IY0"
// "OW1 SH AH0 N" = "OW1 | SH AH0 N"
// "P AH0 T EY1 T OW2" = "P AH0 | T EY1 | T OW2"

// vowels together must be split there
// "S K W ER1 AH0 L" = "S K W ER1 | AH0 L"

// lots of consonants together must be split ???
// "D IH0 K T EY1 T ER0 SH IH2 P" = "D IH0 K | T EY1 | T ER0 | SH IH2 P" - between doubles?
// "K AH1 M F ER0 T AH0 B AH0 L" = "K AH1 M | F ER0 | T AH0 | B AH0 L"
// "K AA1 N S T AH0 B AH0 L" = "K AA1 N | S T AH0 | B AH0 L" - three = split in middle??
// "AH0 T EH1 N SH AH0 N" = "AH0 | T EH1 N | SH AH0 N" - between doubles

export function splitArpabet(arpabet: string): SyllableInfo {
type SyllablePiece = {
part: string;
stressLevel?: number;
};

// split on space
const pieces = arpabet.split(" ").map((c) => c.trim());

// convert pieces to syllable pieces
const syllablePieces: SyllablePiece[] = pieces.map((part) => {
const _stressLevel = Number(part.match(/[0-9]/)?.[0]);
const stressLevel = isNaN(_stressLevel) ? undefined : _stressLevel;

// remove last character if stress level
if (stressLevel !== undefined) {
part = part.slice(0, -1);
}

return { part, stressLevel };
});

// put the pieces into groups

const groups: SyllablePiece[][] = [[]];

let curGroupHasVowel = false;

for (let i = 0; i < syllablePieces.length; i++) {
const prevPiece = syllablePieces[i - 1];
const piece = syllablePieces[i];
const nextPiece = syllablePieces[i + 1];

// determine if current piece should start a new group
const isVowel = piece?.stressLevel !== undefined;

if (curGroupHasVowel) {
const isNextVowel = nextPiece?.stressLevel !== undefined;

const wasPrevVowel = prevPiece?.stressLevel !== undefined;

const nextTwoVowels = isVowel || isNextVowel;

const tripleConsonant = !wasPrevVowel && !isVowel && !isNextVowel;

if (nextTwoVowels || tripleConsonant) {
groups.push([]);
curGroupHasVowel = false;
}
}

const curGroup = groups[groups.length - 1];

if (curGroup === undefined || piece === undefined) {
throw new Error(`curGroup is undefined`);
}

// if piece has stress level, add it to the group

curGroup.push(piece);

if (isVowel) {
curGroupHasVowel = true;
}
}

const syllables = groups.map((group) => group.map((p) => p.part).join(" "));
const stressLevels = groups
.map((group) =>
group.reduce<number | undefined>((acc, p) => {
if (acc !== undefined) {
return acc;
}

return p.stressLevel;
}, undefined)
)
.filter((n) => n !== undefined) as number[];

// create groups of syllables from syllable pieces

return { syllables, stressLevels, arpabet };
}
Loading

0 comments on commit 584ad44

Please sign in to comment.