Skip to content

Commit

Permalink
feat(stories): i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
outloudvi committed Oct 6, 2022
1 parent 70c69f7 commit 0048a6c
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 195 deletions.
53 changes: 53 additions & 0 deletions components/stories/SeasonChapterList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useTranslations } from 'next-intl'
import _range from 'lodash/range'
import { Button } from '@mantine/core'

import { SeriesName } from '#data/stories'

const SeasonChapterList = ({
series,
season,
length,
selected,
completion,
onClick,
}: {
series: SeriesName
season: number
length: number
selected: number | null
completion: Record<number, 0 | 1>
onClick: (c: number) => void
}) => {
const $t = useTranslations('stories')

return (
<div>
<p>
{$t(`series.${series}`)}{' '}
{$t('season', {
s: season,
})}
</p>
{_range(1, length + 1).map((chapter) => {
const currentSelection = chapter === selected
return (
<Button
variant="subtle"
compact
color={completion[chapter - 1] ? 'blue' : 'teal'}
key={chapter}
onClick={() => {
onClick(chapter)
}}
disabled={currentSelection}
>
{season}-{chapter}
</Button>
)
})}
</div>
)
}

export default SeasonChapterList
51 changes: 26 additions & 25 deletions components/stories/StoriesItem.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Skeleton } from '@mantine/core'
import { useTranslations } from 'next-intl'
import { useLocale, useTranslations } from 'next-intl'

import Paths from '#utils/paths'
import { toVideoLink } from '#components/ExternalVideo'
import useApi from '#utils/useApi'
import { SeriesName } from '#data/stories'
import StoriesData from '#data/stories.data'
import AssetImage from '#components/AssetImage'
import useFrontendApi from '#utils/useFrontendApi'
import { ChapterItem } from '#data/types'

type PropType = {
// "Special" won't appear here
Expand All @@ -26,7 +27,6 @@ function getBackendStoryId(props: PropType): string {
Mana: 'st-group-mna-01',
ThreeX: 'st-group-thrx-01',
Tsuki: 'st-group-moon-01',
Special: '',
}
return [
Prefix[series],
Expand All @@ -35,24 +35,16 @@ function getBackendStoryId(props: PropType): string {
].join('-')
}

export const SpecialStoriesItem = (props: {
series: 'Special'
season: number
chapter: number
}) => {
export const SpecialStoriesItem = ({ item }: { item: ChapterItem }) => {
const $c = useTranslations('common')
const { series, season, chapter } = props
const data = StoriesData?.[series]?.[season]?.[chapter]
if (!data) {
return <p className="text-gray-500">找不到故事章节。</p>
}
const { name, video } = item

return (
<>
<div className="text-4xl">{data.name}</div>

<div className="text-4xl">{name}</div>
<p>
<a
href={toVideoLink(data.video)}
href={toVideoLink(video)}
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -66,27 +58,36 @@ export const SpecialStoriesItem = (props: {
const StoriesItem = (props: PropType) => {
const { series, season, chapter } = props
const $t = useTranslations('stories')
const locale = useLocale()

const { data: StoryData, isSuccess } = useApi('Story', {
id: getBackendStoryId(props),
})
const { data: StoryTrnData } = useFrontendApi('stories', {
locale,
series,
season: String(season),
chapter: String(chapter),
})

if (!isSuccess) {
return (
<>
<div className="text-4xl">
{$t(`series.${series}`)} {$t('season', { season })} -{' '}
{$t(`series.${series}`)} {$t('season', { s: season })} -{' '}
{chapter}
</div>
<Skeleton height={200} className="mt-2" />
</>
)
}

const data = StoriesData?.[series]?.[season]?.[chapter]
const subtitle = StoryData?.sectionName

const cnTitle = data?.name && data.name !== 'TODO' ? data.name : null
const localTitle =
StoryTrnData?.name && StoryTrnData.name !== 'TODO'
? StoryTrnData.name
: null
const jaTitle = StoryData?.name?.replace(/\n/g, '')

return (
Expand All @@ -99,9 +100,9 @@ const StoriesItem = (props: PropType) => {
<div className="text-2xl">{subtitle}</div>
<br />
<div className="text-xl">
{cnTitle !== null ? (
{localTitle !== null ? (
<>
<span>{cnTitle}</span> /{' '}
<span>{localTitle}</span> /{' '}
<small>
<span lang="ja">{jaTitle}</span>
</small>
Expand All @@ -115,10 +116,10 @@ const StoriesItem = (props: PropType) => {
<p lang="ja">{StoryData.description}</p>
</div>
)}
{data && (
{StoryTrnData && (
<p>
<a
href={toVideoLink(data.video)}
href={toVideoLink(StoryTrnData.video)}
target="_blank"
rel="noopener noreferrer"
>
Expand All @@ -127,9 +128,9 @@ const StoriesItem = (props: PropType) => {
</p>
)}

{(data === undefined || cnTitle === null) && (
{(StoryTrnData === undefined || localTitle === null) && (
<div className="mt-4 text-gray-500">
尚无{data === undefined ? '剧情' : '标题'}
尚无{StoryTrnData === undefined ? '剧情' : '标题'}
翻译信息。请添加翻译信息到{' '}
<a href={Paths.repo('data/stories.data.ts')}>
data/stories.data.ts
Expand Down
7 changes: 7 additions & 0 deletions components/stories/getSpecialStories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import storiesData from '#data/videos/stories.data'

const getSpecialStories = (locale: string) => {
return storiesData?.[locale]?.special
}

export default getSpecialStories
2 changes: 0 additions & 2 deletions data/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const Series = [
'Mana',
'ThreeX',
'Tsuki',
'Special',
] as const

export type SeriesName = typeof Series[number]
Expand All @@ -29,5 +28,4 @@ export const Episodes: Record<SeriesName, number[]> = {
ThreeX: [20],
// adv_group_moon_
Tsuki: [10],
Special: [3],
}
21 changes: 21 additions & 0 deletions data/videos/stories.data/en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { StoriesData } from './types'

const special: StoriesData['special'] = []

const data: StoriesData['data'] = {
Hoshimi: {},
Tokyo: {},
TRINITYAiLE: {},
LizNoir: {},
Mana: {},
ThreeX: {},
Big4: {},
Tsuki: {},
}

const _ = {
data,
special,
}

export default _
10 changes: 10 additions & 0 deletions data/videos/stories.data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import en from './en'
import zhHans from './zh-hans'
import type { StoriesData } from './types'

const _: Record<string, StoriesData> = {
en: en,
'zh-hans': zhHans,
}

export default _
12 changes: 12 additions & 0 deletions data/videos/stories.data/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { SeriesName } from '#data/stories'
import { ChapterItem } from '#data/types'

export type IStoriesData<T> = Record<
SeriesName,
Record<number, Record<number, T>>
>

export type StoriesData = {
data: IStoriesData<ChapterItem>
special: ChapterItem[]
}
66 changes: 32 additions & 34 deletions data/stories.data.ts → data/videos/stories.data/zh-hans.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
import type { SeriesName } from './stories'
import { ChapterItem } from './types'
import type { StoriesData } from './types'

const data: Partial<
Record<SeriesName, Record<number, Record<number, ChapterItem>>>
> = {
// 其它剧情
Special: {
1: {
1: {
name: '手游开篇剧情',
video: { type: 'bilibili', vid: 'av761315570' },
},
2: {
name: '[2022/4/1] 愚人节春斗剧情(前篇)',
video: { type: 'bilibili', vid: 'av980357709', pid: 1 },
},
3: {
name: '[2022/4/1] 愚人节春斗剧情(后篇)',
video: { type: 'bilibili', vid: 'av980357709', pid: 2 },
},
4: {
name: '[2022/6/30] 三得利售货机限定慰问来电(瑠依)',
video: { type: 'bilibili', vid: 'av812917702' },
},
5: {
name: '[2022/6/30] 三得利售货机限定慰问来电(堇)',
video: { type: 'bilibili', vid: 'av813293495' },
},
6: {
name: '[2022/6/30] 三得利售货机限定慰问来电(优)',
video: { type: 'bilibili', vid: 'av343290005' },
},
},
// 其它剧情
const special: StoriesData['special'] = [
{
name: '手游开篇剧情',
video: { type: 'bilibili', vid: 'av761315570' },
},
{
name: '[2022/4/1] 愚人节春斗剧情(前篇)',
video: { type: 'bilibili', vid: 'av980357709', pid: 1 },
},
{
name: '[2022/4/1] 愚人节春斗剧情(后篇)',
video: { type: 'bilibili', vid: 'av980357709', pid: 2 },
},
{
name: '[2022/6/30] 三得利售货机限定慰问来电(瑠依)',
video: { type: 'bilibili', vid: 'av812917702' },
},
{
name: '[2022/6/30] 三得利售货机限定慰问来电(堇)',
video: { type: 'bilibili', vid: 'av813293495' },
},
{
name: '[2022/6/30] 三得利售货机限定慰问来电(优)',
video: { type: 'bilibili', vid: 'av343290005' },
},
]

const data: StoriesData['data'] = {
Hoshimi: {
1: {
1: {
Expand Down Expand Up @@ -1185,4 +1181,6 @@ const data: Partial<
},
}

export default data
const _ = { data, special }

export default _
7 changes: 5 additions & 2 deletions locales/en/stories.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"Stories": "Stories",
"Others": "Others",
"Video": "Video",
"season": "Season {s}",
"series": {
"Hoshimi": "Hoshimi Series",
"Tokyo": "Tokyo Series",
Expand All @@ -9,6 +13,5 @@
"ThreeX": "ⅢⅩ",
"Tsuki": "Tsuki no Tempest",
"Special": "Others"
},
"season": "Season {s}"
}
}
2 changes: 2 additions & 0 deletions locales/zh-hans/stories.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"Stories": "剧情",
"Others": "其它",
"Video": "视频",
"season": "{s} 章",
"series": {
"Hoshimi": "星见编",
"Tokyo": "东京编",
Expand Down
41 changes: 41 additions & 0 deletions pages/api/stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { withSentry } from '@sentry/nextjs'
import type { NextApiRequest, NextApiResponse } from 'next'

import pickFirstOrOne from '#utils/pickFirstOrOne'
import type { FrontendAPIResponseMapping } from '#utils/useFrontendApi'
import { DEFAULT_LANGUAGE } from '#utils/constants'
import storiesData from '#data/videos/stories.data'
import { SeriesName } from '#data/stories'

// ?series=hoshimi&season=1&chapter=1
const eventStories = async (
req: NextApiRequest,
res: NextApiResponse<FrontendAPIResponseMapping['stories']>
) => {
const q = req.query
const series = q.series
const season = Number.parseInt(String(q.season) ?? '')
const chapter = Number.parseInt(String(q.chapter) ?? '')
const locale = q.locale ? pickFirstOrOne(q.locale) : DEFAULT_LANGUAGE

if (!series || Number.isNaN(season) || Number.isNaN(chapter)) {
res.status(400).end()
return
}

const ret =
storiesData?.[locale]?.data?.[series as SeriesName]?.[season]?.[chapter]
if (!ret) {
res.status(404).end()
return
}
res.status(200).json(ret)
}

export default withSentry(eventStories)

export const config = {
api: {
externalResolver: true,
},
}
Loading

0 comments on commit 0048a6c

Please sign in to comment.