Core library for Pangaea. Provides an AuditRunner class for running globalization analyzers and generating comprehensive audit reports.
The AuditRunner is a static class that orchestrates the execution of multiple analyzers to assess
the globalization readiness of web applications. It generates detailed reports with scores, issues,
and actionable recommendations.
npm install @pangaea-g11n/coreimport { AuditRunner } from '@pangaea-g11n/core';
import type { Analyzer } from '@pangaea-g11n/core';
// Run the audit
const report = await AuditRunner.runAudit(document.body, window.location.href, [rtlAnalyzer]);
console.log(`Overall Score: ${report.globalizationScore}`);
console.log(`Issues Found: ${report.issues.length}`);For testing the analyzers directly in the browser, you can use the IIFE (Immediately Invoked Function Expression) build format.
First, configure your tsup.config.ts to generate an IIFE build:
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['iife'], // IIFE format for direct browser usage
globalName: 'GlobalizationAudit', // Global variable name
platform: 'browser',
outDir: 'dist',
});Build the library:
yarn run buildThis generates dist/index.global.js that can be used directly in the browser.
- Open a website in your browser
- Open Developer Tools (F12)
- Copy the index.global.js content to the console.
- In the console, run:
// create your own analyzer instance
const rtlAnalyzer = new window.GlobalizationAudit.RTLAnalyzer();
// Run on any element
const issues = await rtlAnalyzer.run(document.documentElement);
console.log('Issues found:', issues);https://jbbs.shitaraba.net/bbs/read_archive.cgi/computer/6306/1181816058/
This is a page using EUC-JP encoding with text that looks like mojibake.
static async runAudit(
rootElement: HTMLElement,
url: string,
analyzers: Analyzer[],
analyzerWeights?: Record<string, number>,
analyzerErrorSensitivity?: Record<string, number>,
onProgress?: (progress: AuditProgress) => void
): Promise<AuditReport>rootElement- The root DOM element to auditurl- The URL of the audited pageanalyzers- Array of analyzer instances to runanalyzerWeights(optional) - Custom weights for each analyzer (defaults to equal weights)analyzerErrorSensitivity(optional) - Per-analyzer error sensitivity for score calculation, keyed by analyzer name (defaults to 5 for any analyzer not present in the map, range 1-10)onProgress(optional) - Callback invoked before each analyzer runs, receiving the analyzer name and progress counts
An AuditReport object containing:
{
analyzerScores: Record<string, number>; // Individual scores per analyzer (0-100)
globalizationScore: number; // Overall score (0-100)
timestamp: string; // ISO 8601 timestamp
url: string; // Audited page URL
issues: Issue[]; // All issues found
}import { AuditRunner } from '@pangaea-g11n/core';
import { rtlAnalyzer, encodingAnalyzer, layoutAnalyzer } from './analyzers';
async function auditCurrentPage() {
const report = await AuditRunner.runAudit(
document.body,
window.location.href,
[rtlAnalyzer, encodingAnalyzer, layoutAnalyzer],
{
RTL: 2, // RTL is most important
Encoding: 1.5, // Encoding is moderately important
Layout: 1, // Layout has default importance
},
{ RTL: 5, Encoding: 5, Layout: 5 }, // Default error sensitivity per analyzer
);
// Display results
console.log(`🌍 Globalization Score: ${report.globalizationScore}/100`);
console.log('\n📊 Analyzer Scores:');
Object.entries(report.analyzerScores).forEach(([name, score]) => {
console.log(` ${name}: ${score}/100`);
});
// Group issues by severity
const bySeverity = report.issues.reduce(
(acc, issue) => {
acc[issue.severity] = (acc[issue.severity] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
console.log('\n🔍 Issues Found:');
console.log(` Critical: ${bySeverity.critical || 0}`);
console.log(` Serious: ${bySeverity.serious || 0}`);
console.log(` Warning: ${bySeverity.warning || 0}`);
console.log(` Info: ${bySeverity.info || 0}`);
return report;
}The overall globalization score is calculated as:
- Without weights: Simple average of all analyzer scores
- With weights: Weighted average of analyzer scores
overallScore = (score₁×weight₁ + score₂×weight₂ + ...) / (weight₁ + weight₂ + ...)
Prioritize certain analyzers by assigning them higher weights:
const report = await AuditRunner.runAudit(
document.body,
window.location.href,
[rtlAnalyzer, encodingAnalyzer, layoutAnalyzer],
{
RTL: 2, // RTL has 2x weight
Encoding: 1.5, // Encoding has 1.5x weight
Layout: 1, // Layout has normal weight
},
);
// Overall score = (rtlScore×2 + encodingScore×1.5 + layoutScore×1) / 4.5Adjust how harshly issues affect the score, per analyzer:
// Lenient scoring
const lenientReport = await AuditRunner.runAudit(
document.body,
window.location.href,
[rtlAnalyzer],
undefined, // no custom weights
{ RTLAnalyzer: 2 }, // very lenient
);
// Strict scoring
const strictReport = await AuditRunner.runAudit(
document.body,
window.location.href,
[rtlAnalyzer],
undefined,
{ RTLAnalyzer: 9 }, // very strict
);Issues are grouped by severity when calculating an analyzer's score:
| Severity | Effect on score |
|---|---|
critical |
Any single critical issue immediately drops the analyzer score to 0 |
serious |
Linear deduction — 5 points per issue, no diminishing returns |
moderate |
Diminishing returns per issue type — 3 × sqrt(count) for each issue type |
minor |
Diminishing returns pooled across all types — 2 × sqrt(totalMinorCount) |
info |
No effect on score |
Unless a critical issue is present (which forces the score to 0), the score is:
totalDeduction = (5 × seriousCount)
+ Σ (3 × sqrt(countForType)) over each moderate issue type
+ (2 × sqrt(totalMinorCount))
adjustedDeduction = totalDeduction × (errorSensitivity / 5)
score = clamp(round(100 - adjustedDeduction), 0, 100)
Where errorSensitivity is the per-analyzer sensitivity value (default 5, range 1-10). A value of
5 is neutral (1x multiplier), 10 doubles the deduction, and 1 reduces it to 0.2x.
With default errorSensitivity = 5:
| Issues | Score |
|---|---|
| No issues | 100 |
| 1 info | 100 |
| 1 serious | 95 |
| 1 moderate | 97 |
| 1 minor | 98 |
| 1 critical (any count) | 0 |
| 4 serious | 80 |
Same non-critical issue with different sensitivity (1 serious issue):
| errorSensitivity | Score |
|---|---|
| 1 (lenient) | 99 |
| 5 (default) | 95 |
| 10 (strict) | 90 |
interface Analyzer {
name: string;
run(rootElement: HTMLElement): Promise<Issue[]>;
}interface Issue {
id: string;
type: string;
severity: 'critical' | 'serious' | 'moderate' | 'minor' | 'info';
elementSelector: string;
message?: string;
remediation?: {
docsUrl: string;
suggestion: string;
};
issueMetadata?: Record<string, unknown>;
}interface AuditReport {
analyzerScores: Record<string, number>;
globalizationScore: number;
timestamp: string;
url: string;
issues: Issue[];
}Passed to the optional onProgress callback of AuditRunner.runAudit() before each analyzer runs.
interface AuditProgress {
currentAnalyzer: string;
completedAnalyzers: number;
totalAnalyzers: number;
}