diff --git a/.env b/.env deleted file mode 100644 index 207f076..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -NEXT_PUBLIC_GTM_ID=G-5LPXCW9V8C \ No newline at end of file diff --git a/apps/ziyo-fe/.env b/apps/ziyo-fe/.env index 826a9af..503fd2b 100644 --- a/apps/ziyo-fe/.env +++ b/apps/ziyo-fe/.env @@ -1 +1,2 @@ -NEXT_PUBLIC_API_URL=https://ziyo.nourman.com \ No newline at end of file +NEXT_PUBLIC_API_URL=https://ziyo.nourman.com +NEXT_PUBLIC_GTM_ID=G-5LPXCW9V8C \ No newline at end of file diff --git a/apps/ziyo-fe/next.config.js b/apps/ziyo-fe/next.config.js index 57b5365..c872dde 100644 --- a/apps/ziyo-fe/next.config.js +++ b/apps/ziyo-fe/next.config.js @@ -19,6 +19,14 @@ const nextConfig = { svgr: false, }, transpilePackages: ['@ziyo/ui', 'lucide-react'], + webpack: (config) => { + config.resolve.fallback = { + ...config.resolve.fallback, + net: false, + os: false, + }; + return config; + }, }; const plugins = [ diff --git a/apps/ziyo-fe/src/components/Ruby.tsx b/apps/ziyo-fe/src/components/Ruby.tsx index 3733fc6..bce432f 100644 --- a/apps/ziyo-fe/src/components/Ruby.tsx +++ b/apps/ziyo-fe/src/components/Ruby.tsx @@ -8,9 +8,10 @@ import useSettings from '../hooks/useSettings'; type RubyProps = { rubyString: string; + currentChar: string; }; -export function Ruby({ rubyString }: RubyProps) { +export function Ruby({ rubyString, currentChar }: RubyProps) { const { settings: { preferLatin }, } = useSettings(); @@ -19,7 +20,7 @@ export function Ruby({ rubyString }: RubyProps) { const elements = parts.map((part, index) => { if (index % 2 === 1) { - const [word, ..._readings] = part.split('|'); + const [word, ..._readings] = part.split('|') as [string, ...string[]]; const readings = preferLatin ? _readings.map((r) => toRomaji(r)) @@ -53,10 +54,13 @@ export function Ruby({ rubyString }: RubyProps) { return readings.map((reading, i) => ( - {isHan(word[i]) ? ( + {isHan(word[i]!) ? ( {word[i]} diff --git a/apps/ziyo-fe/src/hooks/query/useGetSentenceList.tsx b/apps/ziyo-fe/src/hooks/query/useGetSentenceList.tsx new file mode 100644 index 0000000..76daca5 --- /dev/null +++ b/apps/ziyo-fe/src/hooks/query/useGetSentenceList.tsx @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/react-query'; +import type { TatoebaResponse } from '@ziyo/types'; + +export function useGetSentenceList({ + query, +}: { + query: { + character: string; + }; +}) { + return useQuery({ + queryKey: ['sentence-list', query], + queryFn: async () => { + const res = await fetch( + `https://api.tatoeba.org/unstable/sentences?lang=jpn&q=${decodeURIComponent( + query.character, + )}&trans=eng&include_unapproved=yes&sort=relevance&limit=20`, + ); + + return (await res.json()) as TatoebaResponse; + }, + enabled: !!query.character && query.character.length > 0, + }); +} diff --git a/apps/ziyo-fe/src/pages/kanji/[character]/index.tsx b/apps/ziyo-fe/src/pages/kanji/[character]/index.tsx index 404d457..d6291a3 100644 --- a/apps/ziyo-fe/src/pages/kanji/[character]/index.tsx +++ b/apps/ziyo-fe/src/pages/kanji/[character]/index.tsx @@ -1,4 +1,3 @@ -import { TatoebaResponse } from '@ziyo/types'; import { Tooltip, TooltipContent, @@ -18,6 +17,7 @@ import { ReadingChip } from '../../../components/ReadingChip'; import { Ruby } from '../../../components/Ruby'; import { Search } from '../../../components/Search'; import { Settings } from '../../../components/Settings'; +import { useGetSentenceList } from '../../../hooks/query/useGetSentenceList'; import { api } from '../../../lib/api'; export const VariantChip = ({ @@ -49,7 +49,7 @@ export const getServerSideProps = async ({ if (!character || Array.isArray(character)) return { notFound: true }; try { - const _kanji = api.kanji.one + const kanji = await api.kanji.one .$get({ query: { character: decodeURIComponent(character), @@ -57,23 +57,9 @@ export const getServerSideProps = async ({ }) .then(async (res) => (await res.json()).data); - const _sentences = fetch( - `https://tatoeba.org/en/api_v0/search?from=jpn&has_audio=&list=3185&native=&orphans=no&query=${decodeURIComponent( - character, - )}&sort=random&sort_reverse=&tags=&to=eng&trans_filter=limit&trans_has_audio=&trans_link=&trans_orphan=&trans_to=eng&trans_unapproved=&trans_user=&unapproved=no&user=&word_count_max=&word_count_min=5`, - { - next: { - revalidate: 0, - }, - }, - ).then(async (res) => TatoebaResponse.parse(await res.json())); - - const [kanji, sentences] = await Promise.all([_kanji, _sentences]); - return { props: { kanji, - sentences, }, }; } catch (e) { @@ -86,17 +72,18 @@ export const getServerSideProps = async ({ export default function KanjiPage({ kanji, - sentences: _sentences, }: InferGetServerSidePropsType) { - const sentences = _sentences.results.map((s) => { - return { - id: s.id, - text: s.transcriptions[0]?.text, - translation: s.translations[0]?.[0]?.text || ( - No translation available - ), - }; + const { data: _sentences } = useGetSentenceList({ + query: { character: kanji.literal }, }); + const sentences = (_sentences?.data ?? []).map((s) => ({ + id: s.id, + text: s.transcriptions.flatMap((t) => t.text)[0] ?? '', + translation: s.translations + .flat(2) + .map((t) => t.text) + .slice(0, 5), + })); const [hoveredVariants, setHoveredVariants] = useState<{ lang: string; @@ -198,7 +185,7 @@ export default function KanjiPage({ voice="jp_001" lang="ja" text={onyomi} - latin={kanji.reading_ja_onyomi_latin[onyomiIdx]} + latin={kanji.reading_ja_onyomi_latin[onyomiIdx]!} className="bg-rose-100 text-gray-900 hover:bg-rose-700 hover:text-gray-100" /> ))} @@ -219,7 +206,7 @@ export default function KanjiPage({ voice="jp_001" lang="ja" text={kunyomi} - latin={kanji.reading_ja_kunyomi_latin[kunyomiIdx]} + latin={kanji.reading_ja_kunyomi_latin[kunyomiIdx]!} other={kunyomi.replace(/^[^.]*\./, kanji.literal)} className="bg-kiiro-200 text-gray-900 hover:bg-kiiro-800 hover:text-gray-100" /> @@ -262,7 +249,7 @@ export default function KanjiPage({ voice="kr_002" lang="ko" text={hangeul} - latin={kanji.reading_ko_latin[hangeulIdx]} + latin={kanji.reading_ko_latin[hangeulIdx]!} className="bg-blue-100 text-gray-900 hover:bg-blue-600 hover:text-gray-100" /> ))} @@ -305,8 +292,10 @@ export default function KanjiPage({ {sentences.map((s) => (
- - {s.translation} + + + {s.translation.map((s) => `"${s}"`).join(', ')} +
))} diff --git a/apps/ziyo-fe/tsconfig.json b/apps/ziyo-fe/tsconfig.json index fd9ec69..17d5560 100644 --- a/apps/ziyo-fe/tsconfig.json +++ b/apps/ziyo-fe/tsconfig.json @@ -6,6 +6,7 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, + "noUncheckedIndexedAccess": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "resolveJsonModule": true, diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts index f2aa263..9fa1e09 100644 --- a/libs/types/src/index.ts +++ b/libs/types/src/index.ts @@ -49,78 +49,45 @@ export const ArrayWithTotalCount = >( }; export const TatoebaResponse = z.object({ - paging: z.object({ - Sentences: z.object({ - finder: z.string(), - page: z.number(), - current: z.number(), - count: z.number(), - perPage: z.number(), - start: z.number(), - end: z.number(), - prevPage: z.boolean(), - nextPage: z.boolean(), - pageCount: z.number(), - sort: z.string(), - direction: z.boolean(), - sortDefault: z.boolean(), - directionDefault: z.boolean(), - }), - }), - results: z.array( + data: z.array( z.object({ id: z.number(), text: z.string(), lang: z.string(), - correctness: z.number(), + script: z.any(), license: z.string(), + transcriptions: z.array( + z.object({ + script: z.string(), + text: z.string(), + needsReview: z.boolean(), + type: z.string(), + html: z.string(), + }), + ), + audios: z.array(z.any()), translations: z.array( z.array( z.object({ id: z.number(), text: z.string(), lang: z.string(), - correctness: z.number(), + script: z.any(), + license: z.string(), + transcriptions: z.array(z.any()), audios: z.array( z.object({ - id: z.number(), author: z.string(), - sentence_id: z.number().optional(), + attribution_url: z.string(), + license: z.string(), + download_url: z.string(), }), ), - isDirect: z.boolean().optional(), - lang_name: z.string(), - dir: z.string(), - lang_tag: z.string(), + owner: z.string().optional(), }), ), ), - transcriptions: z.array( - z.object({ - id: z.number(), - sentence_id: z.number(), - script: z.string(), - text: z.string(), - needsReview: z.boolean(), - modified: z.string(), - - readonly: z.boolean(), - type: z.string(), - html: z.string(), - info_message: z.string(), - }), - ), - audios: z.array( - z.object({ - id: z.number(), - author: z.string(), - }), - ), - lang_name: z.string(), - dir: z.string(), - lang_tag: z.string(), - is_owned_by_current_user: z.boolean(), - max_visible_translations: z.number(), + owner: z.string().optional(), }), ), });