Added more missing localization, added LocalizationChecker tool, moved CriticalCSSGenerator to tools folder
This commit is contained in:
129
src/Server/Tools/CriticalCSS/CriticalCSSGenerator.js
Normal file
129
src/Server/Tools/CriticalCSS/CriticalCSSGenerator.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import { generate } from 'critical';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Login
|
||||
await page.goto('http://localhost:5146/Account/Login');
|
||||
await page.type('#username', 'admin');
|
||||
await page.type('#password', 'UnsafePractice.67');
|
||||
await page.click('button[type=submit]');
|
||||
await page.waitForNavigation();
|
||||
|
||||
// Extract cookies
|
||||
const cookies = await page.cookies();
|
||||
await browser.close();
|
||||
|
||||
async function generateCriticalCSSForViews() {
|
||||
const viewsDir = '../Views';
|
||||
|
||||
// Helper function to get all .cshtml files recursively
|
||||
function getAllCshtmlFiles(dir) {
|
||||
let results = [];
|
||||
const list = fs.readdirSync(dir);
|
||||
|
||||
list.forEach(file => {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
console.log("DEBUG@2");
|
||||
console.log(filePath);
|
||||
if (stat && stat.isDirectory()) {
|
||||
// Recursively get files from subdirectories
|
||||
results = results.concat(getAllCshtmlFiles(filePath));
|
||||
} else if (file.endsWith('.cshtml') && filePath.search("/_") == -1) {
|
||||
results.push(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Helper function to convert file path to URL path
|
||||
function filePathToUrlPath(filePath) {
|
||||
// Remove 'Views/' prefix
|
||||
let relativePath = filePath.replace(/^Views[\/\\]/, '');
|
||||
|
||||
// Remove .cshtml extension
|
||||
relativePath = relativePath.replace(/\.cshtml$/, '');
|
||||
|
||||
// Convert to URL format (replace \ with / and capitalize first letter)
|
||||
const urlPath = relativePath
|
||||
.split(/[\/\\]/)
|
||||
.map((segment, index) =>
|
||||
index === 0 ? segment : segment.charAt(0).toUpperCase() + segment.slice(1)
|
||||
)
|
||||
.join('/');
|
||||
|
||||
// Handle the case where we have a single file (like Index.cshtml)
|
||||
if (relativePath.includes('/')) {
|
||||
// Convert to URL path format: Views/Home/Index.cshtml -> /Home/Index
|
||||
return '/' + relativePath.replace(/\\/g, '/').replace(/\.cshtml$/, '');
|
||||
} else {
|
||||
// For files directly in Views folder (like Views/Index.cshtml)
|
||||
return '/' + relativePath.replace(/\.cshtml$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
// Get all .cshtml files
|
||||
const cshtmlFiles = getAllCshtmlFiles(viewsDir);
|
||||
const criticalCssDir = '.';
|
||||
// if (!fs.existsSync(criticalCssDir)) {
|
||||
// fs.mkdirSync(criticalCssDir, { recursive: true });
|
||||
// }
|
||||
|
||||
// Process each file
|
||||
for (const file of cshtmlFiles) {
|
||||
try {
|
||||
const urlPath = filePathToUrlPath(file).replace("../", "").replace("/Views", "");
|
||||
|
||||
// Generate critical CSS
|
||||
await generate({
|
||||
src: `http://localhost:5146${urlPath}`,
|
||||
inline: false,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
penthouse: {
|
||||
customHeaders: {
|
||||
cookie: cookies.map(c => `${c.name}=${c.value}`).join('; ')
|
||||
},
|
||||
forceExclude: ['.btn'], // Otherwise buttons end up colorless and .btn overrides other classes like .btn-warning, etc. - so it has to be force-excluded here and re-added later
|
||||
forceInclude: [
|
||||
'[data-bs-theme=dark]',
|
||||
'.navbar',
|
||||
'.col-md-4',
|
||||
'.visually-hidden', // visually hidden headings
|
||||
'.bi-info-circle-fill', '.text-info', // info icon
|
||||
'.container', '.col-md-6', '.row', '.g-4', '.row>*',
|
||||
'p', '.fs-3', '.py-4', // title
|
||||
'.mb-4',
|
||||
'.card', '.card-body', '.p-2', // card
|
||||
'h2', '.card-title', '.fs-5', // card - title
|
||||
'.d-flex', '.justify-content-between', '.mt-2', // card - content
|
||||
'.progress', '.mt-3', // card - progress bar
|
||||
'.list-group', '.list-group-flush', '.list-group-item', '.list-group-flush>.list-group-item', '.list-group-flush>.list-group-item:last-child', '.badge', '.bg-warning', '.bg-success', '.h-100', // card - health check list
|
||||
'.btn', '.btn-sm', '.btn-primary', '.btn-warning', '.btn-danger', // Searchdomains buttons
|
||||
'.col-md-8', '.sidebar',
|
||||
'.mb-0', '.mb-2', '.align-items-center',
|
||||
'h3', '.col-md-3', '.col-md-2', '.text-nowrap', '.overflow-auto'
|
||||
]
|
||||
},
|
||||
target: {
|
||||
css: path.join(criticalCssDir, urlPath.replace(/\//g, '.').replace(/^\./, '').replace("...", "") + '.css')
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Critical CSS generated for: ${urlPath}`);
|
||||
} catch (err) {
|
||||
console.error(`Error processing ${file}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('All critical CSS files generated!');
|
||||
}
|
||||
|
||||
// Run the function
|
||||
generateCriticalCSSForViews().catch(console.error);
|
||||
11
src/Server/Tools/CriticalCSS/README.md
Normal file
11
src/Server/Tools/CriticalCSS/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# How to use CriticalCSS
|
||||
1. Install it here
|
||||
```bash
|
||||
npm i -D critical
|
||||
npm install puppeteer
|
||||
```
|
||||
2. Run the css generator:
|
||||
```bash
|
||||
node CriticalCSSGenerator.js
|
||||
```
|
||||
3. Move the `.css` files from the current directory to the `CriticalCSS/` folder (overwrite existing files)
|
||||
78
src/Server/Tools/LocalizationChecker/LocalizationChecker.py
Normal file
78
src/Server/Tools/LocalizationChecker/LocalizationChecker.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
def extract_translations_from_View(view_path):
|
||||
"""Extract all translation strings from file A"""
|
||||
translations = {}
|
||||
|
||||
try:
|
||||
with open(view_path, 'r', encoding='utf-8') as file_a:
|
||||
for line_num, line in enumerate(file_a, 1):
|
||||
# Match T["..."] patterns
|
||||
matches = re.findall(r'T\["([^"]*)"\]', line)
|
||||
for match in matches:
|
||||
translations[match] = line_num
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File {view_path} not found")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error reading file {view_path}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
return translations
|
||||
|
||||
def extract_localizations_from_resource_file(file_b_path):
|
||||
"""Extract all translation strings from file B"""
|
||||
translations = set()
|
||||
|
||||
try:
|
||||
with open(file_b_path, 'r', encoding='utf-8') as file_b:
|
||||
for line in file_b:
|
||||
# Match the pattern in file B
|
||||
match = re.search(r'<data name="([^"]*)"', line)
|
||||
if match:
|
||||
translations.add(match.group(1))
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File {file_b_path} not found")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error reading file {file_b_path}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
return translations
|
||||
|
||||
def find_missing_translations(view, resource):
|
||||
"""Find translations in file A that don't exist in file B"""
|
||||
# Extract translations from both files
|
||||
file_a_translations = extract_translations_from_View(view)
|
||||
file_b_translations = extract_localizations_from_resource_file(resource)
|
||||
|
||||
# Find missing translations
|
||||
missing_translations = []
|
||||
|
||||
for translation_text, line_number in file_a_translations.items():
|
||||
if translation_text not in file_b_translations:
|
||||
missing_translations.append((translation_text, line_number))
|
||||
|
||||
return missing_translations
|
||||
|
||||
def main():
|
||||
views = ["Shared/_Layout.cshtml", "Home/Index.cshtml", "Home/Searchdomains.cshtml"]
|
||||
resources = ["SharedResources.en.resx", "SharedResources.de.resx"]
|
||||
|
||||
print("Checking for missing translations...")
|
||||
print("=" * 50)
|
||||
for view in views:
|
||||
for resource in resources:
|
||||
missing = find_missing_translations("../../Views/" + view, "../../Resources/" + resource)
|
||||
|
||||
if missing:
|
||||
print(f"Found {len(missing)} missing translations in {view}:")
|
||||
print("-" * 50)
|
||||
for translation_text, line_number in missing:
|
||||
print(f"Line {line_number}: T[\"{translation_text}\"]")
|
||||
else:
|
||||
print(f"All localizations in {view} have a matching resource in {resource}!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user