Created
May 25, 2025 04:26
-
-
Save acro5piano/59d2d71489aa118472d3de6251da6f69 to your computer and use it in GitHub Desktop.
simple i18n based on yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' | |
import { dirname, join } from 'path' | |
import { glob } from 'glob' | |
import yaml from 'js-yaml' | |
interface TranslationEntry { | |
key: string | |
files: string[] | |
} | |
// Extract t`...` template strings from file content | |
function extractTranslations( | |
content: string, | |
filePath: string, | |
): TranslationEntry[] { | |
const translations: TranslationEntry[] = [] | |
// Regex to match t`...` template strings | |
// This handles single-line and multi-line template strings | |
const regex = /\bt`([^`]+)`/g | |
let match | |
while ((match = regex.exec(content)) !== null) { | |
const key = match[1] | |
translations.push({ | |
key, | |
files: [filePath], | |
}) | |
} | |
return translations | |
} | |
// Merge translations from multiple files | |
function mergeTranslations( | |
allTranslations: TranslationEntry[], | |
): Map<string, string[]> { | |
const merged = new Map<string, string[]>() | |
for (const entry of allTranslations) { | |
const existing = merged.get(entry.key) || [] | |
merged.set(entry.key, [...existing, ...entry.files]) | |
} | |
return merged | |
} | |
// Load existing translations from YAML | |
function loadExistingTranslations(locale: string): Record<string, string> { | |
const filePath = join('src', 'i18n', `${locale}.yaml`) | |
if (!existsSync(filePath)) { | |
return {} | |
} | |
const content = readFileSync(filePath, 'utf-8') | |
try { | |
const data = yaml.load(content) as Record<string, string> | |
return data || {} | |
} catch (error) { | |
console.error(`Error parsing ${filePath}:`, error) | |
return {} | |
} | |
} | |
// Generate translation YAML file | |
function generateTranslationFile( | |
translations: Map<string, string[]>, | |
existing: Record<string, string>, | |
): string { | |
const translationObject: Record<string, string> = {} | |
// Do not sort keys (newer keys come to the last) | |
const keys = Array.from(translations.keys()) | |
for (const key of keys) { | |
translationObject[key] = existing[key] || '' | |
} | |
const rawYaml = yaml.dump(translationObject, { | |
lineWidth: -1, // Disable line wrapping | |
quotingType: '"', // Use double quotes | |
forceQuotes: false, // Only quote when necessary | |
sortKeys: false, // Keep our sorted order | |
}) | |
return rawYaml.replace(/""/, '|\n') // Replace empty string into newline for readability | |
} | |
async function main() { | |
console.log('Extracting i18n strings...') | |
// Find all .ts, .tsx, and .astro files under src/ | |
const files = await glob('src/**/*.{ts,tsx,astro}', { | |
ignore: ['src/i18n/*.ts', 'src/i18n/*.yaml'], // Ignore translation files | |
}) | |
console.log(`Found ${files.length} files to process`) | |
const allTranslations: TranslationEntry[] = [] | |
// Extract translations from each file | |
for (const file of files) { | |
const content = readFileSync(file, 'utf-8') | |
const translations = extractTranslations(content, file) | |
if (translations.length > 0) { | |
console.log(`Found ${translations.length} translations in ${file}`) | |
allTranslations.push(...translations) | |
} | |
} | |
// Merge translations | |
const mergedTranslations = mergeTranslations(allTranslations) | |
console.log(`Total unique translations: ${mergedTranslations.size}`) | |
// Generate translation files for each locale | |
const locales = ['ja'] // Add more locales as needed | |
for (const locale of locales) { | |
const existing = loadExistingTranslations(locale) | |
const content = generateTranslationFile(mergedTranslations, existing) | |
const outputPath = join('src', 'i18n', `${locale}.yaml`) | |
// Ensure directory exists | |
const dir = dirname(outputPath) | |
if (!existsSync(dir)) { | |
mkdirSync(dir, { recursive: true }) | |
} | |
writeFileSync(outputPath, content) | |
console.log(`Generated ${outputPath}`) | |
// Report missing translations | |
const missingCount = Array.from(mergedTranslations.keys()).filter( | |
(key) => !existing[key] || existing[key] === '', | |
).length | |
if (missingCount > 0) { | |
console.log(`⚠️ ${missingCount} missing translations in ${locale}`) | |
} | |
} | |
console.log('Done!') | |
} | |
// Run the script | |
main().catch(console.error) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
To get started, open the src/pages directory in your project.: | | |
開始するには、 src/pages フォルダを編集してください | |
What's New in Astro 5.0?: | | |
Astro 5.0 での変更点 | |
From content layers to server islands, click to learn more about the new features and improvements in Astro 5.0: | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment