204 lines
7.9 KiB
JavaScript
204 lines
7.9 KiB
JavaScript
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 <link rel="alternate" hreflang="${l}" href="${href}" />`);
|
|
});
|
|
head.append(`\n <link rel="alternate" hreflang="x-default" href="${siteUrl}${file === 'index.html' ? '/' : '/' + file}" />\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 += `
|
|
<url>
|
|
<loc>${cleanLoc}</loc>
|
|
<lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
|
|
<changefreq>weekly</changefreq>
|
|
<priority>${file === 'index.html' ? '1.0' : '0.8'}</priority>
|
|
</url>`;
|
|
});
|
|
});
|
|
|
|
const sitemapContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
${sitemapUrls}
|
|
</urlset>
|
|
`;
|
|
fs.writeFileSync(path.join(rootDir, 'sitemap.xml'), sitemapContent);
|
|
console.log('Sitemap generated!');
|
|
|
|
console.log('Build completed! Language HTML files have been generated.');
|
|
|