feat: add translated names for Devil Fruits and update related schemas
Build Docker Image / build (push) Successful in 1m33s
Build Docker Image / build (push) Successful in 1m33s
- Added support for translated names and French translated names in the DevilFruitRecord and Character interfaces. - Updated JSON import/export scripts to handle new translated name fields. - Enhanced scraping logic to extract translated names from the source. - Modified database schema to include translated names for Devil Fruits. - Updated daily fruit selection to include translated names for characters.
This commit is contained in:
+41
-16
@@ -23,6 +23,8 @@ interface Character {
|
||||
frOrigin: string | null;
|
||||
devilFruitId: string | null;
|
||||
devilFruitName: string | null;
|
||||
devilFruitTranslatedName: string | null;
|
||||
frDevilFruitTranslatedName: string | null;
|
||||
devilFruitUrl: string | null;
|
||||
affiliation: string | null;
|
||||
frAffiliation: string | null;
|
||||
@@ -49,12 +51,16 @@ interface CharacterListItem {
|
||||
interface DevilFruitData {
|
||||
devilFruitId: string;
|
||||
devilFruitTitle: string;
|
||||
devilFruitTranslatedName?: string | null;
|
||||
frDevilFruitTranslatedName?: string | null;
|
||||
devilFruitUrl: string;
|
||||
}
|
||||
|
||||
interface DevilFruit {
|
||||
id: string;
|
||||
name: string;
|
||||
translatedName?: string | null;
|
||||
frTranslatedName?: string | null;
|
||||
type: string | null;
|
||||
url: string;
|
||||
}
|
||||
@@ -173,17 +179,6 @@ function removeParentheticalNotes(text: string): string {
|
||||
return text.replace(/\([^)]*\)/g, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the French link from the API response links array
|
||||
*/
|
||||
|
||||
function getFrLink(links: { lang: string; ['*']: string; url: string }[]): { url: string } | null {
|
||||
// Get french url by getting parse.langlinks where lang is "fr" and extract the name from there
|
||||
const frLink = links.find(
|
||||
(link: { lang: string; ['*']: string; url: string }) => link.lang === 'fr'
|
||||
);
|
||||
return frLink ? { url: frLink['url'] } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize string by decoding URI components, punctuation, and replacing spaces with underscores
|
||||
@@ -485,9 +480,10 @@ async function fetchCharacter(
|
||||
const epithets = extractEpithets($);
|
||||
|
||||
// Extract devil fruit
|
||||
const devilFruitData = await extractDevilFruit($);
|
||||
const devilFruitData = await extractDevilFruit($, 'en');
|
||||
const devilFruitId = devilFruitData?.devilFruitId || null;
|
||||
const devilFruitName = devilFruitData?.devilFruitTitle || null;
|
||||
const devilFruitTranslatedName = devilFruitData?.devilFruitTranslatedName || null;
|
||||
const devilFruitUrl = devilFruitData?.devilFruitUrl || null;
|
||||
|
||||
// Extract haki from JSON categories
|
||||
@@ -546,6 +542,8 @@ async function fetchCharacter(
|
||||
|
||||
const frOrigin = frPage ? extractOrigin(frPage) : null;
|
||||
|
||||
const frDevilFruitTranslatedName = frPage ? (await extractDevilFruit(frPage, 'fr'))?.devilFruitTranslatedName || null : null;
|
||||
|
||||
if (name !== jsonData.parse?.title) {
|
||||
frName = name;
|
||||
}
|
||||
@@ -563,6 +561,8 @@ async function fetchCharacter(
|
||||
frOrigin,
|
||||
devilFruitId,
|
||||
devilFruitName,
|
||||
devilFruitTranslatedName,
|
||||
frDevilFruitTranslatedName,
|
||||
devilFruitUrl,
|
||||
affiliation,
|
||||
frAffiliation,
|
||||
@@ -703,7 +703,7 @@ function extractEpithets($: cheerio.CheerioAPI): string[] {
|
||||
* Extract devil fruit from infobox
|
||||
* Returns both normalized ID and URL
|
||||
*/
|
||||
async function extractDevilFruit($: cheerio.CheerioAPI): Promise<DevilFruitData | null> {
|
||||
async function extractDevilFruit($: cheerio.CheerioAPI, lang: 'en' | 'fr'): Promise<DevilFruitData | null> {
|
||||
// dfname or dfename are used as data-source for devil fruit in the infobox, we check both to be safe
|
||||
const fruit = $('[data-source="dfname"] .pi-data-value, [data-source="dfename"] .pi-data-value').first();
|
||||
const link = fruit.find('a').first();
|
||||
@@ -712,6 +712,20 @@ async function extractDevilFruit($: cheerio.CheerioAPI): Promise<DevilFruitData
|
||||
let fruitTitle = fruit.text().trim().toLowerCase().includes('smile') ? fruit.text().trim() : link.text().trim();
|
||||
if (!fruitTitle) return null;
|
||||
|
||||
let translatedName = null;
|
||||
|
||||
if (lang === 'en') {
|
||||
const translatedNameBox = $('[data-source="dfename"] .pi-data-value').first();
|
||||
if (translatedNameBox.length > 0) {
|
||||
translatedName = translatedNameBox.text().trim();
|
||||
}
|
||||
} else if (lang === 'fr') {
|
||||
const translatedNameBox = $('[data-source="dfnomf"] .pi-data-value').first();
|
||||
if (translatedNameBox.length > 0) {
|
||||
translatedName = translatedNameBox.text().trim();
|
||||
}
|
||||
}
|
||||
|
||||
const href = link.attr('href');
|
||||
if (!href || !href.startsWith('/wiki/')) return null;
|
||||
|
||||
@@ -727,6 +741,7 @@ async function extractDevilFruit($: cheerio.CheerioAPI): Promise<DevilFruitData
|
||||
return {
|
||||
devilFruitId: normalizeId(fruitTitle),
|
||||
devilFruitTitle: fruitTitle,
|
||||
devilFruitTranslatedName: translatedName || null,
|
||||
devilFruitUrl: cleanUrl
|
||||
};
|
||||
}
|
||||
@@ -918,6 +933,8 @@ async function saveToCSV(characters: Character[]): Promise<void> {
|
||||
async function fetchDevilFruit(
|
||||
devilFruitUrl: string,
|
||||
devilFruitTitle: string,
|
||||
devilFruitTranslatedName: string | null,
|
||||
frdevilFruitTranslatedName: string | null,
|
||||
devilFruitId: string
|
||||
): Promise<DevilFruit | null> {
|
||||
try {
|
||||
@@ -956,6 +973,8 @@ async function fetchDevilFruit(
|
||||
return {
|
||||
id: devilFruitId,
|
||||
name: devilFruitTitle.toLowerCase().includes('smile') ? devilFruitTitle : name,
|
||||
translatedName: devilFruitTranslatedName || null,
|
||||
frTranslatedName: frdevilFruitTranslatedName || null,
|
||||
type,
|
||||
url: devilFruitUrl
|
||||
};
|
||||
@@ -984,6 +1003,8 @@ async function saveDevilFruitsToCSV(devilFruits: DevilFruit[]): Promise<void> {
|
||||
header: [
|
||||
{ id: 'id', title: 'ID' },
|
||||
{ id: 'name', title: 'Name' },
|
||||
{ id: 'translatedName', title: 'Translated Name' },
|
||||
{ id: 'frTranslatedName', title: 'Translated Name (FR)' },
|
||||
{ id: 'type', title: 'Type' },
|
||||
{ id: 'url', title: 'URL' }
|
||||
]
|
||||
@@ -994,6 +1015,8 @@ async function saveDevilFruitsToCSV(devilFruits: DevilFruit[]): Promise<void> {
|
||||
.map((df) => ({
|
||||
id: df.id || '',
|
||||
name: df.name || '',
|
||||
translatedName: df.translatedName || '',
|
||||
frTranslatedName: df.frTranslatedName || '',
|
||||
type: df.type || '',
|
||||
url: df.url || ''
|
||||
}));
|
||||
@@ -1056,7 +1079,9 @@ async function main(): Promise<void> {
|
||||
const name = c.devilFruitName!;
|
||||
const url = c.devilFruitUrl!;
|
||||
const id = normalizeId(name);
|
||||
return [`${normalizeId(url)}::${id}`, { url, name, id }] as const;
|
||||
const translatedName = c.devilFruitTranslatedName || null;
|
||||
const frTranslatedName = c.frDevilFruitTranslatedName || null;
|
||||
return [`${normalizeId(url)}::${id}`, { url, name, id, translatedName, frTranslatedName }] as const;
|
||||
})
|
||||
)
|
||||
.values()
|
||||
@@ -1064,7 +1089,7 @@ async function main(): Promise<void> {
|
||||
console.log(`✓ Found ${devilFruitEntries.length} unique devil fruits\n`);
|
||||
|
||||
// Step 3: Scraping Devil Fruits
|
||||
console.log('=== Step 2: Scraping Devil Fruits ===\n');
|
||||
console.log('=== Step 3: Scraping Devil Fruits ===\n');
|
||||
|
||||
if (devilFruitEntries.length === 0) {
|
||||
console.warn('No devil fruits found from characters, skipping...\n');
|
||||
@@ -1075,7 +1100,7 @@ async function main(): Promise<void> {
|
||||
const batch = devilFruitEntries.slice(i, i + FETCH_CONCURRENCY);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map(async (entry) => {
|
||||
const data = await fetchDevilFruit(entry.url, entry.name, entry.id);
|
||||
const data = await fetchDevilFruit(entry.url, entry.name, entry.translatedName, entry.frTranslatedName, entry.id);
|
||||
return { entry, data };
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user