const fs = require('fs'); const path = require('path'); const cheerio = require('cheerio'); const langs = ['ko', 'en', 'ja', 'zh']; const defaultLang = 'ko'; const siteUrl = 'https://minglestudio.co.kr'; const rootDir = __dirname; const i18nDir = path.join(rootDir, 'i18n'); const componentsDir = path.join(rootDir, 'components'); // Read all html files const htmlFiles = fs.readdirSync(rootDir).filter(f => f.endsWith('.html') && f !== 'naver-site-verification.html'); // Load translations const translations = {}; langs.forEach(lang => { try { translations[lang] = JSON.parse(fs.readFileSync(path.join(i18nDir, `${lang}.json`), 'utf-8')); } catch (e) { console.error(`Failed to load ${lang}.json`, e); } }); // Load components const headerHtml = fs.readFileSync(path.join(componentsDir, 'header.html'), 'utf-8'); const footerHtml = fs.readFileSync(path.join(componentsDir, 'footer.html'), 'utf-8'); function t(lang, key, fallback = '') { if (lang === defaultLang) return fallback || key; const keys = key.split('.'); let val = translations[lang]; if (!val) return fallback || key; for (const k of keys) { if (val && typeof val === 'object' && k in val) { val = val[k]; } else { return fallback || key; } } return typeof val === 'string' ? val : (fallback || key); } console.log('Starting i18n SSG Build...'); langs.forEach(lang => { const isDefault = lang === defaultLang; const outDir = isDefault ? rootDir : path.join(rootDir, lang); if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } htmlFiles.forEach(file => { const pageName = file.replace('.html', '') || 'index'; const content = fs.readFileSync(path.join(rootDir, file), 'utf-8'); const $ = cheerio.load(content, { decodeEntities: false }); // Change lang attribute $('html').attr('lang', lang); // Remove existing hreflang tags if any to prevent duplicates $('link[rel="alternate"][hreflang]').remove(); // Add hreflang tags const head = $('head'); langs.forEach(l => { const href = l === defaultLang ? `${siteUrl}${file === 'index.html' ? '/' : '/' + file}` : `${siteUrl}/${l}${file === 'index.html' ? '/' : '/' + file}`; head.append(`\n `); }); head.append(`\n \n`); // Change canonical const canonicalHref = isDefault ? `${siteUrl}${file === 'index.html' ? '/' : '/' + file}` : `${siteUrl}/${lang}${file === 'index.html' ? '/' : '/' + file}`; $('link[rel="canonical"]').attr('href', canonicalHref); // Inject components if ($('#header-placeholder').html()?.trim() === '') { $('#header-placeholder').html('\n' + headerHtml + '\n'); } if ($('#footer-placeholder').html()?.trim() === '') { $('#footer-placeholder').html('\n' + footerHtml + '\n'); } // Translate nodes (only if not default language) if (!isDefault) { $('[data-i18n]').each((i, el) => { const key = $(el).attr('data-i18n'); const trans = t(lang, key, $(el).text()); if (trans.includes('<')) $(el).html(trans); else $(el).text(trans); }); $('[data-i18n-html]').each((i, el) => { const key = $(el).attr('data-i18n-html'); const trans = t(lang, key, $(el).html()); $(el).html(trans); }); $('[data-i18n-placeholder]').each((i, el) => { const key = $(el).attr('data-i18n-placeholder'); $(el).attr('placeholder', t(lang, key, $(el).attr('placeholder'))); }); $('[data-i18n-aria]').each((i, el) => { const key = $(el).attr('data-i18n-aria'); $(el).attr('aria-label', t(lang, key, $(el).attr('aria-label') || '')); }); $('[data-i18n-title]').each((i, el) => { const key = $(el).attr('data-i18n-title'); $(el).attr('title', t(lang, key, $(el).attr('title') || '')); }); // Meta tags const titleKey = `${pageName}.meta.title`; const tTitle = t(lang, titleKey, null); if (tTitle) $('title').text(tTitle); const descKey = `${pageName}.meta.description`; const tDesc = t(lang, descKey, null); if (tDesc) $('meta[name="description"]').attr('content', tDesc); const ogTitleKey = `${pageName}.meta.ogTitle`; const tOgTitle = t(lang, ogTitleKey, null); if (tOgTitle) $('meta[property="og:title"]').attr('content', tOgTitle); const ogDescKey = `${pageName}.meta.ogDescription`; const tOgDesc = t(lang, ogDescKey, null); if (tOgDesc) $('meta[property="og:description"]').attr('content', tOgDesc); } if (!isDefault) { // Rewrite asset paths $('link[href^="css/"]').attr('href', (i, val) => '/' + val); $('link[href^="components/"]').attr('href', (i, val) => '/' + val); $('script[src^="js/"]').attr('src', (i, val) => '/' + val); $('img[src^="images/"]').attr('src', (i, val) => '/' + val); // Re-map internal links to proper language folder $('a[href^="/"], a.nav-link').each((i, el) => { const href = $(el).attr('href'); if (!href) return; // If it's a root-relative link (e.g., /about, /contact) if (href.startsWith('/') && href.length > 1 && !href.startsWith('/images') && !href.startsWith('/css') && !href.startsWith('/js') && !href.startsWith('/i18n')) { const newHref = `/${lang}${href}`; $(el).attr('href', newHref); } else if (href === '/') { $(el).attr('href', `/${lang}/`); } else if (!href.startsWith('http') && !href.startsWith('#') && !href.startsWith('/') && !href.startsWith('tel:') && !href.startsWith('mailto:')) { // For links like href="about.html" or href="contact.html", we need to change it if (href.endsWith('.html')) { const newHref = `/${lang}/${href.replace('.html', '')}`; $(el).attr('href', newHref); } } }); } fs.writeFileSync(path.join(outDir, file), $.html()); console.log(`[${lang}] Processed ${file}`); }); }); console.log('Generating sitemap.xml...'); let sitemapUrls = ''; langs.forEach(lang => { htmlFiles.forEach(file => { const isDefault = lang === defaultLang; const pageRoute = file === 'index.html' ? '' : file.replace('.html', ''); const loc = isDefault ? `${siteUrl}/${pageRoute}` : `${siteUrl}/${lang}/${pageRoute}`; // Remove trailing slash if it's just root, but wait siteUrl + '/' for root const cleanLoc = loc.endsWith('/') ? loc : (pageRoute === '' && loc === siteUrl ? `${siteUrl}/` : loc); sitemapUrls += ` ${cleanLoc} ${new Date().toISOString().split('T')[0]} weekly ${file === 'index.html' ? '1.0' : '0.8'} `; }); }); const sitemapContent = ` ${sitemapUrls} `; fs.writeFileSync(path.join(rootDir, 'sitemap.xml'), sitemapContent); console.log('Sitemap generated!'); console.log('Build completed! Language HTML files have been generated.');