hensei-web/components/reps/CharacterRep/index.tsx
Justin Edmund 3d67622353
Fix i18n migration to next-intl (#430)
## Summary
- Fixed translation key format compatibility with next-intl
- Fixed pluralization format from i18next to next-intl format
- Fixed dynamic translation key error handling
- Updated server components to match API response structure
- Fixed useSearchParams import location

## Changes
- Changed pluralization from `{{count}} items` to `{count} items` format
- Added proper error handling for missing translation keys
- Fixed import paths for next-intl hooks
- Fixed PartyPageClient trying to set non-existent appState.parties

## Test plan
- [x] Verified translations render correctly
- [x] Tested pluralization works with different counts
- [x] Confirmed no console errors about missing translations
- [x] Tested party page functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-03 16:25:59 -07:00

137 lines
3.5 KiB
TypeScript

'use client'
import React, { useEffect, useState } from 'react'
import { getCookie } from 'cookies-next'
import { useTranslations } from 'next-intl'
import 'fix-date'
import styles from './index.module.scss'
import classNames from 'classnames'
interface Props {
job?: Job
gender?: number
element?: number
grid: GridArray<GridCharacter>
}
const CHARACTERS_COUNT = 3
const CharacterRep = (props: Props) => {
// Localization for alt tags
const t = useTranslations('common')
const locale = (getCookie('NEXT_LOCALE') as string) || 'en'
// Component state
const [characters, setCharacters] = useState<GridArray<Character>>({})
const [grid, setGrid] = useState<GridArray<GridCharacter>>({})
// On grid update
useEffect(() => {
const newCharacters = Array(CHARACTERS_COUNT)
const gridCharacters = Array(CHARACTERS_COUNT)
if (props.grid) {
for (const [key, value] of Object.entries(props.grid)) {
if (value) {
newCharacters[value.position] = value.object
gridCharacters[value.position] = value
}
}
}
setCharacters(newCharacters)
setGrid(gridCharacters)
}, [props.grid])
// Convert element to string
function numberToElement() {
switch (props.element) {
case 1:
return 'wind'
case 2:
return 'fire'
case 3:
return 'water'
case 4:
return 'earth'
case 5:
return 'dark'
case 6:
return 'light'
default:
return ''
}
}
// Methods: Image generation
function generateMCImage() {
let source = ''
if (props.job) {
const slug = props.job.name.en.replaceAll(' ', '-').toLowerCase()
const gender = props.gender == 1 ? 'b' : 'a'
source = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-portraits/${slug}_${gender}.png`
}
return (
props.job &&
props.job.id !== '-1' && (
<img alt={props.job ? props.job?.name[locale] : ''} src={source} />
)
)
}
function generateGridImage(position: number) {
let url = ''
const character = characters[position]
const gridCharacter = grid[position]
if (character && gridCharacter) {
// Change the image based on the uncap level
let suffix = '01'
if (gridCharacter.transcendence_step > 0) suffix = '04'
else if (gridCharacter.uncap_level >= 5) suffix = '03'
else if (gridCharacter.uncap_level > 2) suffix = '02'
if (character.element == 0) {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${props.element}.jpg`
} else {
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg`
}
}
return characters[position] ? (
<img alt={characters[position]?.name[locale]} src={url} />
) : (
''
)
}
// Render
return (
<div className={styles.rep}>
<ul className={styles.characters}>
<li
key="characters-job"
className={classNames({
[styles.protagonist]: true,
[styles[`${numberToElement()}`]]: true,
[styles.empty]: !props.job || props.job.id === '-1',
})}
>
{generateMCImage()}
</li>
{Array.from(Array(CHARACTERS_COUNT)).map((x, i) => {
return (
<li key={`characters-${i}`} className={styles.character}>
{generateGridImage(i)}
</li>
)
})}
</ul>
</div>
)
}
export default CharacterRep