@paulojalowyj/openkit 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/ARCHITECTURE.md +150 -0
- package/.opencode/LICENSE +21 -0
- package/.opencode/bin/cli.js +213 -0
- package/.opencode/commands/README.md +273 -0
- package/.opencode/commands/analyze.md +64 -0
- package/.opencode/commands/brainstorm.md +186 -0
- package/.opencode/commands/checklist.md +62 -0
- package/.opencode/commands/clarify.md +40 -0
- package/.opencode/commands/context.md +68 -0
- package/.opencode/commands/create.md +70 -0
- package/.opencode/commands/debug.md +160 -0
- package/.opencode/commands/deploy.md +244 -0
- package/.opencode/commands/doc.md +45 -0
- package/.opencode/commands/engineer.md +483 -0
- package/.opencode/commands/impl.md +242 -0
- package/.opencode/commands/plan.md +250 -0
- package/.opencode/commands/preview.md +87 -0
- package/.opencode/commands/specify.md +66 -0
- package/.opencode/commands/status.md +103 -0
- package/.opencode/commands/tasks.md +58 -0
- package/.opencode/commands/test.md +104 -0
- package/.opencode/commands/ui-ux.md +216 -0
- package/.opencode/prompts/backend-specialist.md +315 -0
- package/.opencode/prompts/chat.md +36 -0
- package/.opencode/prompts/database-architect.md +244 -0
- package/.opencode/prompts/debugger.md +244 -0
- package/.opencode/prompts/devops-engineer.md +259 -0
- package/.opencode/prompts/documentation-writer.md +121 -0
- package/.opencode/prompts/explorer-agent.md +92 -0
- package/.opencode/prompts/frontend-specialist.md +608 -0
- package/.opencode/prompts/mobile-developer.md +393 -0
- package/.opencode/prompts/orchestrator.md +472 -0
- package/.opencode/prompts/penetration-tester.md +205 -0
- package/.opencode/prompts/performance-optimizer.md +204 -0
- package/.opencode/prompts/product-owner.md +113 -0
- package/.opencode/prompts/project-planner.md +413 -0
- package/.opencode/prompts/security-auditor.md +187 -0
- package/.opencode/prompts/seo-specialist.md +128 -0
- package/.opencode/prompts/test-engineer.md +190 -0
- package/.opencode/rules/AGENT_TEMPLATE.md +391 -0
- package/.opencode/rules/MASTER.md +272 -0
- package/.opencode/rules/README.md +266 -0
- package/.opencode/rules/TODOLIST_EXAMPLES.md +675 -0
- package/.opencode/rules/TODOLIST_PROTOCOL.md +495 -0
- package/.opencode/rules/TOOL_USAGE.md +731 -0
- package/.opencode/scripts/auto_preview.py +100 -0
- package/.opencode/scripts/checklist.py +217 -0
- package/.opencode/scripts/session_manager.py +225 -0
- package/.opencode/scripts/verify_all.py +403 -0
- package/.opencode/skills/api-patterns/SKILL.md +80 -0
- package/.opencode/skills/api-patterns/api-style.md +42 -0
- package/.opencode/skills/api-patterns/auth.md +24 -0
- package/.opencode/skills/api-patterns/documentation.md +26 -0
- package/.opencode/skills/api-patterns/graphql.md +41 -0
- package/.opencode/skills/api-patterns/rate-limiting.md +31 -0
- package/.opencode/skills/api-patterns/response.md +37 -0
- package/.opencode/skills/api-patterns/rest.md +40 -0
- package/.opencode/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.opencode/skills/api-patterns/security-testing.md +122 -0
- package/.opencode/skills/api-patterns/trpc.md +41 -0
- package/.opencode/skills/api-patterns/versioning.md +22 -0
- package/.opencode/skills/app-builder/SKILL.md +101 -0
- package/.opencode/skills/app-builder/agent-coordination.md +71 -0
- package/.opencode/skills/app-builder/feature-building.md +53 -0
- package/.opencode/skills/app-builder/project-detection.md +34 -0
- package/.opencode/skills/app-builder/scaffolding.md +116 -0
- package/.opencode/skills/app-builder/tech-stack.md +40 -0
- package/.opencode/skills/app-builder/templates/SKILL.md +39 -0
- package/.opencode/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.opencode/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.opencode/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.opencode/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.opencode/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.opencode/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.opencode/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.opencode/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +81 -0
- package/.opencode/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
- package/.opencode/skills/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
- package/.opencode/skills/app-builder/templates/nuxt-app/TEMPLATE.md +100 -0
- package/.opencode/skills/app-builder/templates/python-fastapi/TEMPLATE.md +82 -0
- package/.opencode/skills/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
- package/.opencode/skills/architecture/SKILL.md +55 -0
- package/.opencode/skills/architecture/context-discovery.md +43 -0
- package/.opencode/skills/architecture/examples.md +94 -0
- package/.opencode/skills/architecture/pattern-selection.md +68 -0
- package/.opencode/skills/architecture/patterns-reference.md +50 -0
- package/.opencode/skills/architecture/trade-off-analysis.md +77 -0
- package/.opencode/skills/bash-linux/SKILL.md +199 -0
- package/.opencode/skills/behavioral-modes/SKILL.md +242 -0
- package/.opencode/skills/brainstorming/SKILL.md +163 -0
- package/.opencode/skills/brainstorming/dynamic-questioning.md +350 -0
- package/.opencode/skills/clean-code/SKILL.md +201 -0
- package/.opencode/skills/code-review-checklist/SKILL.md +109 -0
- package/.opencode/skills/database-design/SKILL.md +73 -0
- package/.opencode/skills/database-design/database-selection.md +43 -0
- package/.opencode/skills/database-design/indexing.md +39 -0
- package/.opencode/skills/database-design/migrations.md +48 -0
- package/.opencode/skills/database-design/optimization.md +36 -0
- package/.opencode/skills/database-design/orm-selection.md +30 -0
- package/.opencode/skills/database-design/schema-design.md +56 -0
- package/.opencode/skills/database-design/scripts/schema_validator.py +172 -0
- package/.opencode/skills/deployment-procedures/SKILL.md +241 -0
- package/.opencode/skills/documentation-templates/SKILL.md +279 -0
- package/.opencode/skills/frontend-design/SKILL.md +446 -0
- package/.opencode/skills/frontend-design/animation-guide.md +331 -0
- package/.opencode/skills/frontend-design/color-system.md +311 -0
- package/.opencode/skills/frontend-design/data/charts.csv +26 -0
- package/.opencode/skills/frontend-design/data/colors.csv +97 -0
- package/.opencode/skills/frontend-design/data/icons.csv +101 -0
- package/.opencode/skills/frontend-design/data/landing.csv +31 -0
- package/.opencode/skills/frontend-design/data/products.csv +97 -0
- package/.opencode/skills/frontend-design/data/prompts.csv +24 -0
- package/.opencode/skills/frontend-design/data/react-performance.csv +45 -0
- package/.opencode/skills/frontend-design/data/stacks/flutter.csv +53 -0
- package/.opencode/skills/frontend-design/data/stacks/html-tailwind.csv +56 -0
- package/.opencode/skills/frontend-design/data/stacks/jetpack-compose.csv +53 -0
- package/.opencode/skills/frontend-design/data/stacks/nextjs.csv +53 -0
- package/.opencode/skills/frontend-design/data/stacks/nuxt-ui.csv +51 -0
- package/.opencode/skills/frontend-design/data/stacks/nuxtjs.csv +59 -0
- package/.opencode/skills/frontend-design/data/stacks/react-native.csv +52 -0
- package/.opencode/skills/frontend-design/data/stacks/react.csv +54 -0
- package/.opencode/skills/frontend-design/data/stacks/shadcn.csv +61 -0
- package/.opencode/skills/frontend-design/data/stacks/svelte.csv +54 -0
- package/.opencode/skills/frontend-design/data/stacks/swiftui.csv +51 -0
- package/.opencode/skills/frontend-design/data/stacks/vue.csv +50 -0
- package/.opencode/skills/frontend-design/data/styles.csv +59 -0
- package/.opencode/skills/frontend-design/data/typography.csv +58 -0
- package/.opencode/skills/frontend-design/data/ui-reasoning.csv +101 -0
- package/.opencode/skills/frontend-design/data/ux-guidelines.csv +100 -0
- package/.opencode/skills/frontend-design/data/web-interface.csv +31 -0
- package/.opencode/skills/frontend-design/decision-trees.md +418 -0
- package/.opencode/skills/frontend-design/motion-graphics.md +306 -0
- package/.opencode/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.opencode/skills/frontend-design/scripts/core.py +258 -0
- package/.opencode/skills/frontend-design/scripts/design_system.py +1067 -0
- package/.opencode/skills/frontend-design/scripts/search.py +106 -0
- package/.opencode/skills/frontend-design/scripts/ux_audit.py +735 -0
- package/.opencode/skills/frontend-design/typography-system.md +345 -0
- package/.opencode/skills/frontend-design/ux-psychology.md +541 -0
- package/.opencode/skills/frontend-design/visual-effects.md +383 -0
- package/.opencode/skills/geo-fundamentals/SKILL.md +156 -0
- package/.opencode/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.opencode/skills/i18n-localization/SKILL.md +154 -0
- package/.opencode/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.opencode/skills/intelligent-routing/SKILL.md +335 -0
- package/.opencode/skills/lint-and-validate/SKILL.md +45 -0
- package/.opencode/skills/lint-and-validate/scripts/lint_runner.py +172 -0
- package/.opencode/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.opencode/skills/mobile-design/SKILL.md +394 -0
- package/.opencode/skills/mobile-design/decision-trees.md +516 -0
- package/.opencode/skills/mobile-design/mobile-backend.md +491 -0
- package/.opencode/skills/mobile-design/mobile-color-system.md +420 -0
- package/.opencode/skills/mobile-design/mobile-debugging.md +122 -0
- package/.opencode/skills/mobile-design/mobile-design-thinking.md +357 -0
- package/.opencode/skills/mobile-design/mobile-navigation.md +458 -0
- package/.opencode/skills/mobile-design/mobile-performance.md +767 -0
- package/.opencode/skills/mobile-design/mobile-testing.md +356 -0
- package/.opencode/skills/mobile-design/mobile-typography.md +433 -0
- package/.opencode/skills/mobile-design/platform-android.md +666 -0
- package/.opencode/skills/mobile-design/platform-ios.md +561 -0
- package/.opencode/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.opencode/skills/mobile-design/touch-psychology.md +537 -0
- package/.opencode/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +312 -0
- package/.opencode/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -0
- package/.opencode/skills/nextjs-react-expert/3-server-server-side-performance.md +490 -0
- package/.opencode/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -0
- package/.opencode/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/.opencode/skills/nextjs-react-expert/6-rendering-rendering-performance.md +432 -0
- package/.opencode/skills/nextjs-react-expert/7-js-javascript-performance.md +684 -0
- package/.opencode/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -0
- package/.opencode/skills/nextjs-react-expert/SKILL.md +267 -0
- package/.opencode/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/.opencode/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/.opencode/skills/parallel-agents/SKILL.md +175 -0
- package/.opencode/skills/performance-profiling/SKILL.md +143 -0
- package/.opencode/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.opencode/skills/plan-writing/SKILL.md +176 -0
- package/.opencode/skills/python-patterns/SKILL.md +462 -0
- package/.opencode/skills/red-team-tactics/SKILL.md +199 -0
- package/.opencode/skills/seo-fundamentals/SKILL.md +129 -0
- package/.opencode/skills/seo-fundamentals/scripts/seo_checker.py +222 -0
- package/.opencode/skills/server-management/SKILL.md +161 -0
- package/.opencode/skills/stack-selection/SKILL.md +448 -0
- package/.opencode/skills/systematic-debugging/SKILL.md +109 -0
- package/.opencode/skills/tailwind-patterns/SKILL.md +269 -0
- package/.opencode/skills/tdd-workflow/SKILL.md +149 -0
- package/.opencode/skills/testing-patterns/SKILL.md +178 -0
- package/.opencode/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.opencode/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.opencode/skills/vulnerability-scanner/checklists.md +121 -0
- package/.opencode/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/.opencode/skills/web-design-guidelines/SKILL.md +57 -0
- package/.opencode/skills/webapp-testing/SKILL.md +187 -0
- package/.opencode/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.opencode/templates/DOCS-ACTION_ITEMS.md +5 -0
- package/.opencode/templates/DOCS-API.md +11 -0
- package/.opencode/templates/DOCS-BACKEND.md +10 -0
- package/.opencode/templates/DOCS-CONTEXT.md +25 -0
- package/.opencode/templates/DOCS-DATABASE.md +10 -0
- package/.opencode/templates/DOCS-FRONTEND.md +11 -0
- package/.opencode/templates/DOCS-QUALITY_GATES.md +20 -0
- package/.opencode/templates/DOCS-SECURITY.md +17 -0
- package/.opencode/templates/SDD-AcceptanceCriteria.md +21 -0
- package/.opencode/templates/SDD-Checklist.md +27 -0
- package/.opencode/templates/SDD-Contracts.md +21 -0
- package/.opencode/templates/SDD-Plan.md +45 -0
- package/.opencode/templates/SDD-ProblemStatement.md +25 -0
- package/.opencode/templates/SDD-Quickstart.md +23 -0
- package/.opencode/templates/SDD-Research.md +24 -0
- package/.opencode/templates/SDD-Risks.md +16 -0
- package/.opencode/templates/SDD-Tasks.md +41 -0
- package/.opencode/templates/SDD-UserStories.md +45 -0
- package/.opencode/templates/TechStack.md +111 -0
- package/LICENSE +21 -0
- package/PACKAGE_STATUS.md +97 -0
- package/README.md +251 -0
- package/README.pt-BR.md +192 -0
- package/bin/cli.js +505 -0
- package/blueprints/fullstack/.env.example +15 -0
- package/blueprints/fullstack/AGENTS.md +3 -0
- package/blueprints/fullstack/README.md +65 -0
- package/blueprints/fullstack/backend/.dockerignore +10 -0
- package/blueprints/fullstack/backend/.python-version +1 -0
- package/blueprints/fullstack/backend/Dockerfile +33 -0
- package/blueprints/fullstack/backend/alembic.ini +40 -0
- package/blueprints/fullstack/backend/app/__init__.py +0 -0
- package/blueprints/fullstack/backend/app/api/README.md +3 -0
- package/blueprints/fullstack/backend/app/api/__init__.py +0 -0
- package/blueprints/fullstack/backend/app/celery_app.py +5 -0
- package/blueprints/fullstack/backend/app/core/README.md +3 -0
- package/blueprints/fullstack/backend/app/core/__init__.py +0 -0
- package/blueprints/fullstack/backend/app/database.py +14 -0
- package/blueprints/fullstack/backend/app/main.py +16 -0
- package/blueprints/fullstack/backend/app/models/README.md +3 -0
- package/blueprints/fullstack/backend/app/models/__init__.py +3 -0
- package/blueprints/fullstack/backend/app/models/item.py +10 -0
- package/blueprints/fullstack/backend/app/routers/__init__.py +0 -0
- package/blueprints/fullstack/backend/app/routers/items.py +20 -0
- package/blueprints/fullstack/backend/app/schemas/README.md +3 -0
- package/blueprints/fullstack/backend/app/schemas/__init__.py +0 -0
- package/blueprints/fullstack/backend/app/schemas/item.py +15 -0
- package/blueprints/fullstack/backend/app/services/item_service.py +23 -0
- package/blueprints/fullstack/backend/app/settings.py +36 -0
- package/blueprints/fullstack/backend/app/tasks/README.md +3 -0
- package/blueprints/fullstack/backend/app/tasks/__init__.py +0 -0
- package/blueprints/fullstack/backend/migrations/env.py +47 -0
- package/blueprints/fullstack/backend/migrations/versions/0001_initial_sample_data.py +31 -0
- package/blueprints/fullstack/backend/pyproject.toml +45 -0
- package/blueprints/fullstack/docker-compose.dev.yml +114 -0
- package/blueprints/fullstack/docker-compose.prod.yml +90 -0
- package/blueprints/fullstack/docs/README.md +29 -0
- package/blueprints/fullstack/docs/engineering/api/README.md +3 -0
- package/blueprints/fullstack/docs/engineering/architecture/README.md +3 -0
- package/blueprints/fullstack/docs/engineering/backend/README.md +3 -0
- package/blueprints/fullstack/docs/engineering/frontend/README.md +3 -0
- package/blueprints/fullstack/docs/engineering/security/README.md +3 -0
- package/blueprints/fullstack/docs/engineering/standards/README.md +3 -0
- package/blueprints/fullstack/frontend/.dockerignore +4 -0
- package/blueprints/fullstack/frontend/Dockerfile +23 -0
- package/blueprints/fullstack/frontend/components.json +17 -0
- package/blueprints/fullstack/frontend/index.html +12 -0
- package/blueprints/fullstack/frontend/package.json +28 -0
- package/blueprints/fullstack/frontend/src/components/README.md +3 -0
- package/blueprints/fullstack/frontend/src/components/ui/.keep +4 -0
- package/blueprints/fullstack/frontend/src/index.css +57 -0
- package/blueprints/fullstack/frontend/src/lib/README.md +3 -0
- package/blueprints/fullstack/frontend/src/lib/api.ts +1 -0
- package/blueprints/fullstack/frontend/src/lib/utils.ts +6 -0
- package/blueprints/fullstack/frontend/src/main.tsx +66 -0
- package/blueprints/fullstack/frontend/src/routes/README.md +3 -0
- package/blueprints/fullstack/frontend/src/routes/root.tsx +7 -0
- package/blueprints/fullstack/frontend/src/vite-env.d.ts +1 -0
- package/blueprints/fullstack/frontend/tailwind.config.ts +12 -0
- package/blueprints/fullstack/frontend/tsconfig.json +13 -0
- package/blueprints/fullstack/frontend/tsconfig.node.json +12 -0
- package/blueprints/fullstack/frontend/vite.config.ts +12 -0
- package/index.js +14 -0
- package/opencode.json +306 -0
- package/package.json +57 -0
- package/scripts/prepare.js +65 -0
- package/scripts/update-version.js +29 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
GEO Checker - Generative Engine Optimization Audit
|
|
4
|
+
Checks PUBLIC WEB CONTENT for AI citation readiness.
|
|
5
|
+
|
|
6
|
+
PURPOSE:
|
|
7
|
+
- Analyze pages that will be INDEXED by AI engines (ChatGPT, Perplexity, etc.)
|
|
8
|
+
- Check for structured data, author info, dates, FAQ sections
|
|
9
|
+
- Help content rank in AI-generated answers
|
|
10
|
+
|
|
11
|
+
WHAT IT CHECKS:
|
|
12
|
+
- HTML files (actual web pages)
|
|
13
|
+
- JSX/TSX files (React page components)
|
|
14
|
+
- NOT markdown files (those are developer docs, not public content)
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
python geo_checker.py <project_path>
|
|
18
|
+
"""
|
|
19
|
+
import sys
|
|
20
|
+
import re
|
|
21
|
+
import json
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
# Fix Windows console encoding
|
|
25
|
+
try:
|
|
26
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
27
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
28
|
+
except AttributeError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Directories to skip (not public content)
|
|
33
|
+
SKIP_DIRS = {
|
|
34
|
+
'node_modules', '.next', 'dist', 'build', '.git', '.github',
|
|
35
|
+
'__pycache__', '.vscode', '.idea', 'coverage', 'test', 'tests',
|
|
36
|
+
'__tests__', 'spec', 'docs', 'documentation'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Files to skip (not public pages)
|
|
40
|
+
SKIP_FILES = {
|
|
41
|
+
'jest.config', 'webpack.config', 'vite.config', 'tsconfig',
|
|
42
|
+
'package.json', 'package-lock', 'yarn.lock', '.eslintrc',
|
|
43
|
+
'tailwind.config', 'postcss.config', 'next.config'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_page_file(file_path: Path) -> bool:
|
|
48
|
+
"""Check if this file is likely a public-facing page."""
|
|
49
|
+
name = file_path.stem.lower()
|
|
50
|
+
|
|
51
|
+
# Skip config/utility files
|
|
52
|
+
if any(skip in name for skip in SKIP_FILES):
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
# Skip test files
|
|
56
|
+
if name.endswith('.test') or name.endswith('.spec'):
|
|
57
|
+
return False
|
|
58
|
+
if name.startswith('test_') or name.startswith('spec_'):
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
# Likely page indicators
|
|
62
|
+
page_indicators = ['page', 'index', 'home', 'about', 'contact', 'blog',
|
|
63
|
+
'post', 'article', 'product', 'service', 'landing']
|
|
64
|
+
|
|
65
|
+
# Check if it's in a pages/app directory (Next.js, etc.)
|
|
66
|
+
parts = [p.lower() for p in file_path.parts]
|
|
67
|
+
if 'pages' in parts or 'app' in parts or 'routes' in parts:
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
# Check filename indicators
|
|
71
|
+
if any(ind in name for ind in page_indicators):
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
# HTML files are usually pages
|
|
75
|
+
if file_path.suffix.lower() == '.html':
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def find_web_pages(project_path: Path) -> list:
|
|
82
|
+
"""Find public-facing web pages only."""
|
|
83
|
+
patterns = ['**/*.html', '**/*.htm', '**/*.jsx', '**/*.tsx']
|
|
84
|
+
|
|
85
|
+
files = []
|
|
86
|
+
for pattern in patterns:
|
|
87
|
+
for f in project_path.glob(pattern):
|
|
88
|
+
# Skip excluded directories
|
|
89
|
+
if any(skip in f.parts for skip in SKIP_DIRS):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Check if it's likely a page
|
|
93
|
+
if is_page_file(f):
|
|
94
|
+
files.append(f)
|
|
95
|
+
|
|
96
|
+
return files[:30] # Limit to 30 pages
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_page(file_path: Path) -> dict:
|
|
100
|
+
"""Check a single web page for GEO elements."""
|
|
101
|
+
try:
|
|
102
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
103
|
+
except Exception as e:
|
|
104
|
+
return {'file': str(file_path.name), 'passed': [], 'issues': [f"Error: {e}"], 'score': 0}
|
|
105
|
+
|
|
106
|
+
issues = []
|
|
107
|
+
passed = []
|
|
108
|
+
|
|
109
|
+
# 1. JSON-LD Structured Data (Critical for AI)
|
|
110
|
+
if 'application/ld+json' in content:
|
|
111
|
+
passed.append("JSON-LD structured data found")
|
|
112
|
+
if '"@type"' in content:
|
|
113
|
+
if 'Article' in content:
|
|
114
|
+
passed.append("Article schema present")
|
|
115
|
+
if 'FAQPage' in content:
|
|
116
|
+
passed.append("FAQ schema present")
|
|
117
|
+
if 'Organization' in content or 'Person' in content:
|
|
118
|
+
passed.append("Entity schema present")
|
|
119
|
+
else:
|
|
120
|
+
issues.append("No JSON-LD structured data (AI engines prefer structured content)")
|
|
121
|
+
|
|
122
|
+
# 2. Heading Structure
|
|
123
|
+
h1_count = len(re.findall(r'<h1[^>]*>', content, re.I))
|
|
124
|
+
h2_count = len(re.findall(r'<h2[^>]*>', content, re.I))
|
|
125
|
+
|
|
126
|
+
if h1_count == 1:
|
|
127
|
+
passed.append("Single H1 heading (clear topic)")
|
|
128
|
+
elif h1_count == 0:
|
|
129
|
+
issues.append("No H1 heading - page topic unclear")
|
|
130
|
+
else:
|
|
131
|
+
issues.append(f"Multiple H1 headings ({h1_count}) - confusing for AI")
|
|
132
|
+
|
|
133
|
+
if h2_count >= 2:
|
|
134
|
+
passed.append(f"{h2_count} H2 subheadings (good structure)")
|
|
135
|
+
else:
|
|
136
|
+
issues.append("Add more H2 subheadings for scannable content")
|
|
137
|
+
|
|
138
|
+
# 3. Author Attribution (E-E-A-T signal)
|
|
139
|
+
author_patterns = ['author', 'byline', 'written-by', 'contributor', 'rel="author"']
|
|
140
|
+
has_author = any(p in content.lower() for p in author_patterns)
|
|
141
|
+
if has_author:
|
|
142
|
+
passed.append("Author attribution found")
|
|
143
|
+
else:
|
|
144
|
+
issues.append("No author info (AI prefers attributed content)")
|
|
145
|
+
|
|
146
|
+
# 4. Publication Date (Freshness signal)
|
|
147
|
+
date_patterns = ['datePublished', 'dateModified', 'datetime=', 'pubdate', 'article:published']
|
|
148
|
+
has_date = any(re.search(p, content, re.I) for p in date_patterns)
|
|
149
|
+
if has_date:
|
|
150
|
+
passed.append("Publication date found")
|
|
151
|
+
else:
|
|
152
|
+
issues.append("No publication date (freshness matters for AI)")
|
|
153
|
+
|
|
154
|
+
# 5. FAQ Section (Highly citable)
|
|
155
|
+
faq_patterns = [r'<details', r'faq', r'frequently.?asked', r'"FAQPage"']
|
|
156
|
+
has_faq = any(re.search(p, content, re.I) for p in faq_patterns)
|
|
157
|
+
if has_faq:
|
|
158
|
+
passed.append("FAQ section detected (highly citable)")
|
|
159
|
+
|
|
160
|
+
# 6. Lists (Structured content)
|
|
161
|
+
list_count = len(re.findall(r'<(ul|ol)[^>]*>', content, re.I))
|
|
162
|
+
if list_count >= 2:
|
|
163
|
+
passed.append(f"{list_count} lists (structured content)")
|
|
164
|
+
|
|
165
|
+
# 7. Tables (Comparison data)
|
|
166
|
+
table_count = len(re.findall(r'<table[^>]*>', content, re.I))
|
|
167
|
+
if table_count >= 1:
|
|
168
|
+
passed.append(f"{table_count} table(s) (comparison data)")
|
|
169
|
+
|
|
170
|
+
# 8. Entity Recognition (E-E-A-T signal) - NEW 2025
|
|
171
|
+
entity_patterns = [
|
|
172
|
+
r'"@type"\s*:\s*"Organization"',
|
|
173
|
+
r'"@type"\s*:\s*"LocalBusiness"',
|
|
174
|
+
r'"@type"\s*:\s*"Brand"',
|
|
175
|
+
r'itemtype.*schema\.org/(Organization|Person|Brand)',
|
|
176
|
+
r'rel="author"'
|
|
177
|
+
]
|
|
178
|
+
has_entity = any(re.search(p, content, re.I) for p in entity_patterns)
|
|
179
|
+
if has_entity:
|
|
180
|
+
passed.append("Entity/Brand recognition (E-E-A-T)")
|
|
181
|
+
|
|
182
|
+
# 9. Original Statistics/Data (AI citation magnet) - NEW 2025
|
|
183
|
+
stat_patterns = [
|
|
184
|
+
r'\d+%', # Percentages
|
|
185
|
+
r'\$[\d,]+', # Dollar amounts
|
|
186
|
+
r'study\s+(shows|found)', # Research citations
|
|
187
|
+
r'according to', # Source attribution
|
|
188
|
+
r'data\s+(shows|reveals)', # Data-backed claims
|
|
189
|
+
r'\d+x\s+(faster|better|more)', # Comparison stats
|
|
190
|
+
r'(million|billion|trillion)', # Large numbers
|
|
191
|
+
]
|
|
192
|
+
stat_matches = sum(1 for p in stat_patterns if re.search(p, content, re.I))
|
|
193
|
+
if stat_matches >= 2:
|
|
194
|
+
passed.append("Original statistics/data (citation magnet)")
|
|
195
|
+
|
|
196
|
+
# 10. Conversational/Direct answers - NEW 2025
|
|
197
|
+
direct_answer_patterns = [
|
|
198
|
+
r'is defined as',
|
|
199
|
+
r'refers to',
|
|
200
|
+
r'means that',
|
|
201
|
+
r'the answer is',
|
|
202
|
+
r'in short,',
|
|
203
|
+
r'simply put,',
|
|
204
|
+
r'<dfn'
|
|
205
|
+
]
|
|
206
|
+
has_direct = any(re.search(p, content, re.I) for p in direct_answer_patterns)
|
|
207
|
+
if has_direct:
|
|
208
|
+
passed.append("Direct answer patterns (LLM-friendly)")
|
|
209
|
+
|
|
210
|
+
# Calculate score
|
|
211
|
+
total = len(passed) + len(issues)
|
|
212
|
+
score = (len(passed) / total * 100) if total > 0 else 0
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
'file': str(file_path.name),
|
|
216
|
+
'passed': passed,
|
|
217
|
+
'issues': issues,
|
|
218
|
+
'score': round(score)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
target = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
224
|
+
target_path = Path(target).resolve()
|
|
225
|
+
|
|
226
|
+
print("\n" + "=" * 60)
|
|
227
|
+
print(" GEO CHECKER - AI Citation Readiness Audit")
|
|
228
|
+
print("=" * 60)
|
|
229
|
+
print(f"Project: {target_path}")
|
|
230
|
+
print("-" * 60)
|
|
231
|
+
|
|
232
|
+
# Find web pages only
|
|
233
|
+
pages = find_web_pages(target_path)
|
|
234
|
+
|
|
235
|
+
if not pages:
|
|
236
|
+
print("\n[!] No public web pages found.")
|
|
237
|
+
print(" Looking for: HTML, JSX, TSX files in pages/app directories")
|
|
238
|
+
print(" Skipping: docs, tests, config files, node_modules")
|
|
239
|
+
output = {"script": "geo_checker", "pages_found": 0, "passed": True}
|
|
240
|
+
print("\n" + json.dumps(output, indent=2))
|
|
241
|
+
sys.exit(0)
|
|
242
|
+
|
|
243
|
+
print(f"Found {len(pages)} public pages to analyze\n")
|
|
244
|
+
|
|
245
|
+
# Check each page
|
|
246
|
+
results = []
|
|
247
|
+
for page in pages:
|
|
248
|
+
result = check_page(page)
|
|
249
|
+
results.append(result)
|
|
250
|
+
|
|
251
|
+
# Print results
|
|
252
|
+
for result in results:
|
|
253
|
+
status = "[OK]" if result['score'] >= 60 else "[!]"
|
|
254
|
+
print(f"{status} {result['file']}: {result['score']}%")
|
|
255
|
+
if result['issues'] and result['score'] < 60:
|
|
256
|
+
for issue in result['issues'][:2]: # Show max 2 issues
|
|
257
|
+
print(f" - {issue}")
|
|
258
|
+
|
|
259
|
+
# Average score
|
|
260
|
+
avg_score = sum(r['score'] for r in results) / len(results) if results else 0
|
|
261
|
+
|
|
262
|
+
print("\n" + "=" * 60)
|
|
263
|
+
print(f"AVERAGE GEO SCORE: {avg_score:.0f}%")
|
|
264
|
+
print("=" * 60)
|
|
265
|
+
|
|
266
|
+
if avg_score >= 80:
|
|
267
|
+
print("[OK] Excellent - Content well-optimized for AI citations")
|
|
268
|
+
elif avg_score >= 60:
|
|
269
|
+
print("[OK] Good - Some improvements recommended")
|
|
270
|
+
elif avg_score >= 40:
|
|
271
|
+
print("[!] Needs work - Add structured elements")
|
|
272
|
+
else:
|
|
273
|
+
print("[X] Poor - Content needs GEO optimization")
|
|
274
|
+
|
|
275
|
+
# JSON output
|
|
276
|
+
output = {
|
|
277
|
+
"script": "geo_checker",
|
|
278
|
+
"project": str(target_path),
|
|
279
|
+
"pages_checked": len(results),
|
|
280
|
+
"average_score": round(avg_score),
|
|
281
|
+
"passed": avg_score >= 60
|
|
282
|
+
}
|
|
283
|
+
print("\n" + json.dumps(output, indent=2))
|
|
284
|
+
|
|
285
|
+
sys.exit(0 if avg_score >= 60 else 1)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: i18n-localization
|
|
3
|
+
description: Internationalization and localization patterns. Detecting hardcoded strings, managing translations, locale files, RTL support.
|
|
4
|
+
allowed-tools: Read, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# i18n & Localization
|
|
8
|
+
|
|
9
|
+
> Internationalization (i18n) and Localization (L10n) best practices.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Core Concepts
|
|
14
|
+
|
|
15
|
+
| Term | Meaning |
|
|
16
|
+
|------|---------|
|
|
17
|
+
| **i18n** | Internationalization - making app translatable |
|
|
18
|
+
| **L10n** | Localization - actual translations |
|
|
19
|
+
| **Locale** | Language + Region (en-US, tr-TR) |
|
|
20
|
+
| **RTL** | Right-to-left languages (Arabic, Hebrew) |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. When to Use i18n
|
|
25
|
+
|
|
26
|
+
| Project Type | i18n Needed? |
|
|
27
|
+
|--------------|--------------|
|
|
28
|
+
| Public web app | Yes |
|
|
29
|
+
| SaaS product | Yes |
|
|
30
|
+
| Internal tool | Maybe |
|
|
31
|
+
| Single-region app | Consider future |
|
|
32
|
+
| Personal project | Optional |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 3. Implementation Patterns
|
|
37
|
+
|
|
38
|
+
### React (react-i18next)
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { useTranslation } from 'react-i18next';
|
|
42
|
+
|
|
43
|
+
function Welcome() {
|
|
44
|
+
const { t } = useTranslation();
|
|
45
|
+
return <h1>{t('welcome.title')}</h1>;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Next.js (next-intl)
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { useTranslations } from 'next-intl';
|
|
53
|
+
|
|
54
|
+
export default function Page() {
|
|
55
|
+
const t = useTranslations('Home');
|
|
56
|
+
return <h1>{t('title')}</h1>;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Python (gettext)
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from gettext import gettext as _
|
|
64
|
+
|
|
65
|
+
print(_("Welcome to our app"))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 4. File Structure
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
locales/
|
|
74
|
+
├── en/
|
|
75
|
+
│ ├── common.json
|
|
76
|
+
│ ├── auth.json
|
|
77
|
+
│ └── errors.json
|
|
78
|
+
├── tr/
|
|
79
|
+
│ ├── common.json
|
|
80
|
+
│ ├── auth.json
|
|
81
|
+
│ └── errors.json
|
|
82
|
+
└── ar/ # RTL
|
|
83
|
+
└── ...
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 5. Best Practices
|
|
89
|
+
|
|
90
|
+
### DO
|
|
91
|
+
|
|
92
|
+
- Use translation keys, not raw text
|
|
93
|
+
- Namespace translations by feature
|
|
94
|
+
- Support pluralization
|
|
95
|
+
- Handle date/number formats per locale
|
|
96
|
+
- Plan for RTL from the start
|
|
97
|
+
- Use ICU message format for complex strings
|
|
98
|
+
|
|
99
|
+
### DON'T
|
|
100
|
+
|
|
101
|
+
- Hardcode strings in components
|
|
102
|
+
- Concatenate translated strings
|
|
103
|
+
- Assume text length (German is 30% longer)
|
|
104
|
+
- Forget about RTL layout
|
|
105
|
+
- Mix languages in same file
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 6. Common Issues
|
|
110
|
+
|
|
111
|
+
| Issue | Solution |
|
|
112
|
+
|-------|----------|
|
|
113
|
+
| Missing translation | Fallback to default language |
|
|
114
|
+
| Hardcoded strings | Use linter/checker script |
|
|
115
|
+
| Date format | Use Intl.DateTimeFormat |
|
|
116
|
+
| Number format | Use Intl.NumberFormat |
|
|
117
|
+
| Pluralization | Use ICU message format |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 7. RTL Support
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
/* CSS Logical Properties */
|
|
125
|
+
.container {
|
|
126
|
+
margin-inline-start: 1rem; /* Not margin-left */
|
|
127
|
+
padding-inline-end: 1rem; /* Not padding-right */
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[dir="rtl"] .icon {
|
|
131
|
+
transform: scaleX(-1);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 8. Checklist
|
|
138
|
+
|
|
139
|
+
Before shipping:
|
|
140
|
+
|
|
141
|
+
- [ ] All user-facing strings use translation keys
|
|
142
|
+
- [ ] Locale files exist for all supported languages
|
|
143
|
+
- [ ] Date/number formatting uses Intl API
|
|
144
|
+
- [ ] RTL layout tested (if applicable)
|
|
145
|
+
- [ ] Fallback language configured
|
|
146
|
+
- [ ] No hardcoded strings in components
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Script
|
|
151
|
+
|
|
152
|
+
| Script | Purpose | Command |
|
|
153
|
+
|--------|---------|---------|
|
|
154
|
+
| `scripts/i18n_checker.py` | Detect hardcoded strings & missing translations | `python scripts/i18n_checker.py <project_path>` |
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
i18n Checker - Detects hardcoded strings and missing translations.
|
|
4
|
+
Scans for untranslated text in React, Vue, and Python files.
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Fix Windows console encoding for Unicode output
|
|
12
|
+
try:
|
|
13
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
14
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
15
|
+
except AttributeError:
|
|
16
|
+
pass # Python < 3.7
|
|
17
|
+
|
|
18
|
+
# Patterns that indicate hardcoded strings (should be translated)
|
|
19
|
+
HARDCODED_PATTERNS = {
|
|
20
|
+
'jsx': [
|
|
21
|
+
# Text directly in JSX: <div>Hello World</div>
|
|
22
|
+
r'>\s*[A-Z][a-zA-Z\s]{3,30}\s*</',
|
|
23
|
+
# JSX attribute strings: title="Welcome"
|
|
24
|
+
r'(title|placeholder|label|alt|aria-label)="[A-Z][a-zA-Z\s]{2,}"',
|
|
25
|
+
# Button/heading text
|
|
26
|
+
r'<(button|h[1-6]|p|span|label)[^>]*>\s*[A-Z][a-zA-Z\s!?.,]{3,}\s*</',
|
|
27
|
+
],
|
|
28
|
+
'vue': [
|
|
29
|
+
# Vue template text
|
|
30
|
+
r'>\s*[A-Z][a-zA-Z\s]{3,30}\s*</',
|
|
31
|
+
r'(placeholder|label|title)="[A-Z][a-zA-Z\s]{2,}"',
|
|
32
|
+
],
|
|
33
|
+
'python': [
|
|
34
|
+
# print/raise with string literals
|
|
35
|
+
r'(print|raise\s+\w+)\s*\(\s*["\'][A-Z][^"\']{5,}["\']',
|
|
36
|
+
# Flask flash messages
|
|
37
|
+
r'flash\s*\(\s*["\'][A-Z][^"\']{5,}["\']',
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Patterns that indicate proper i18n usage
|
|
42
|
+
I18N_PATTERNS = [
|
|
43
|
+
r't\(["\']', # t('key') - react-i18next
|
|
44
|
+
r'useTranslation', # React hook
|
|
45
|
+
r'\$t\(', # Vue i18n
|
|
46
|
+
r'_\(["\']', # Python gettext
|
|
47
|
+
r'gettext\(', # Python gettext
|
|
48
|
+
r'useTranslations', # next-intl
|
|
49
|
+
r'FormattedMessage', # react-intl
|
|
50
|
+
r'i18n\.', # Generic i18n
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
def find_locale_files(project_path: Path) -> list:
|
|
54
|
+
"""Find translation/locale files."""
|
|
55
|
+
patterns = [
|
|
56
|
+
"**/locales/**/*.json",
|
|
57
|
+
"**/translations/**/*.json",
|
|
58
|
+
"**/lang/**/*.json",
|
|
59
|
+
"**/i18n/**/*.json",
|
|
60
|
+
"**/messages/*.json",
|
|
61
|
+
"**/*.po", # gettext
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
files = []
|
|
65
|
+
for pattern in patterns:
|
|
66
|
+
files.extend(project_path.glob(pattern))
|
|
67
|
+
|
|
68
|
+
return [f for f in files if 'node_modules' not in str(f)]
|
|
69
|
+
|
|
70
|
+
def check_locale_completeness(locale_files: list) -> dict:
|
|
71
|
+
"""Check if all locales have the same keys."""
|
|
72
|
+
issues = []
|
|
73
|
+
passed = []
|
|
74
|
+
|
|
75
|
+
if not locale_files:
|
|
76
|
+
return {'passed': [], 'issues': ["[!] No locale files found"]}
|
|
77
|
+
|
|
78
|
+
# Group by parent folder (language)
|
|
79
|
+
locales = {}
|
|
80
|
+
for f in locale_files:
|
|
81
|
+
if f.suffix == '.json':
|
|
82
|
+
try:
|
|
83
|
+
lang = f.parent.name
|
|
84
|
+
content = json.loads(f.read_text(encoding='utf-8'))
|
|
85
|
+
if lang not in locales:
|
|
86
|
+
locales[lang] = {}
|
|
87
|
+
locales[lang][f.stem] = set(flatten_keys(content))
|
|
88
|
+
except:
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
if len(locales) < 2:
|
|
92
|
+
passed.append(f"[OK] Found {len(locale_files)} locale file(s)")
|
|
93
|
+
return {'passed': passed, 'issues': issues}
|
|
94
|
+
|
|
95
|
+
passed.append(f"[OK] Found {len(locales)} language(s): {', '.join(locales.keys())}")
|
|
96
|
+
|
|
97
|
+
# Compare keys across locales
|
|
98
|
+
all_langs = list(locales.keys())
|
|
99
|
+
base_lang = all_langs[0]
|
|
100
|
+
|
|
101
|
+
for namespace in locales.get(base_lang, {}):
|
|
102
|
+
base_keys = locales[base_lang].get(namespace, set())
|
|
103
|
+
|
|
104
|
+
for lang in all_langs[1:]:
|
|
105
|
+
other_keys = locales.get(lang, {}).get(namespace, set())
|
|
106
|
+
|
|
107
|
+
missing = base_keys - other_keys
|
|
108
|
+
if missing:
|
|
109
|
+
issues.append(f"[X] {lang}/{namespace}: Missing {len(missing)} keys")
|
|
110
|
+
|
|
111
|
+
extra = other_keys - base_keys
|
|
112
|
+
if extra:
|
|
113
|
+
issues.append(f"[!] {lang}/{namespace}: {len(extra)} extra keys")
|
|
114
|
+
|
|
115
|
+
if not issues:
|
|
116
|
+
passed.append("[OK] All locales have matching keys")
|
|
117
|
+
|
|
118
|
+
return {'passed': passed, 'issues': issues}
|
|
119
|
+
|
|
120
|
+
def flatten_keys(d, prefix=''):
|
|
121
|
+
"""Flatten nested dict keys."""
|
|
122
|
+
keys = set()
|
|
123
|
+
for k, v in d.items():
|
|
124
|
+
new_key = f"{prefix}.{k}" if prefix else k
|
|
125
|
+
if isinstance(v, dict):
|
|
126
|
+
keys.update(flatten_keys(v, new_key))
|
|
127
|
+
else:
|
|
128
|
+
keys.add(new_key)
|
|
129
|
+
return keys
|
|
130
|
+
|
|
131
|
+
def check_hardcoded_strings(project_path: Path) -> dict:
|
|
132
|
+
"""Check for hardcoded strings in code files."""
|
|
133
|
+
issues = []
|
|
134
|
+
passed = []
|
|
135
|
+
|
|
136
|
+
# Find code files
|
|
137
|
+
extensions = {
|
|
138
|
+
'.tsx': 'jsx', '.jsx': 'jsx', '.ts': 'jsx', '.js': 'jsx',
|
|
139
|
+
'.vue': 'vue',
|
|
140
|
+
'.py': 'python'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
code_files = []
|
|
144
|
+
for ext in extensions:
|
|
145
|
+
code_files.extend(project_path.rglob(f"*{ext}"))
|
|
146
|
+
|
|
147
|
+
code_files = [f for f in code_files if not any(x in str(f) for x in
|
|
148
|
+
['node_modules', '.git', 'dist', 'build', '__pycache__', 'venv', 'test', 'spec'])]
|
|
149
|
+
|
|
150
|
+
if not code_files:
|
|
151
|
+
return {'passed': ["[!] No code files found"], 'issues': []}
|
|
152
|
+
|
|
153
|
+
files_with_i18n = 0
|
|
154
|
+
files_with_hardcoded = 0
|
|
155
|
+
hardcoded_examples = []
|
|
156
|
+
|
|
157
|
+
for file_path in code_files[:50]: # Limit
|
|
158
|
+
try:
|
|
159
|
+
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
|
160
|
+
ext = file_path.suffix
|
|
161
|
+
file_type = extensions.get(ext, 'jsx')
|
|
162
|
+
|
|
163
|
+
# Check for i18n usage
|
|
164
|
+
has_i18n = any(re.search(p, content) for p in I18N_PATTERNS)
|
|
165
|
+
if has_i18n:
|
|
166
|
+
files_with_i18n += 1
|
|
167
|
+
|
|
168
|
+
# Check for hardcoded strings
|
|
169
|
+
patterns = HARDCODED_PATTERNS.get(file_type, [])
|
|
170
|
+
hardcoded_found = False
|
|
171
|
+
|
|
172
|
+
for pattern in patterns:
|
|
173
|
+
matches = re.findall(pattern, content)
|
|
174
|
+
if matches and not has_i18n:
|
|
175
|
+
hardcoded_found = True
|
|
176
|
+
if len(hardcoded_examples) < 5:
|
|
177
|
+
hardcoded_examples.append(f"{file_path.name}: {str(matches[0])[:40]}...")
|
|
178
|
+
|
|
179
|
+
if hardcoded_found:
|
|
180
|
+
files_with_hardcoded += 1
|
|
181
|
+
|
|
182
|
+
except:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
passed.append(f"[OK] Analyzed {len(code_files)} code files")
|
|
186
|
+
|
|
187
|
+
if files_with_i18n > 0:
|
|
188
|
+
passed.append(f"[OK] {files_with_i18n} files use i18n")
|
|
189
|
+
|
|
190
|
+
if files_with_hardcoded > 0:
|
|
191
|
+
issues.append(f"[X] {files_with_hardcoded} files may have hardcoded strings")
|
|
192
|
+
for ex in hardcoded_examples:
|
|
193
|
+
issues.append(f" → {ex}")
|
|
194
|
+
else:
|
|
195
|
+
passed.append("[OK] No obvious hardcoded strings detected")
|
|
196
|
+
|
|
197
|
+
return {'passed': passed, 'issues': issues}
|
|
198
|
+
|
|
199
|
+
def main():
|
|
200
|
+
target = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
201
|
+
project_path = Path(target)
|
|
202
|
+
|
|
203
|
+
print("\n" + "=" * 60)
|
|
204
|
+
print(" i18n CHECKER - Internationalization Audit")
|
|
205
|
+
print("=" * 60 + "\n")
|
|
206
|
+
|
|
207
|
+
# Check locale files
|
|
208
|
+
locale_files = find_locale_files(project_path)
|
|
209
|
+
locale_result = check_locale_completeness(locale_files)
|
|
210
|
+
|
|
211
|
+
# Check hardcoded strings
|
|
212
|
+
code_result = check_hardcoded_strings(project_path)
|
|
213
|
+
|
|
214
|
+
# Print results
|
|
215
|
+
print("[LOCALE FILES]")
|
|
216
|
+
print("-" * 40)
|
|
217
|
+
for item in locale_result['passed']:
|
|
218
|
+
print(f" {item}")
|
|
219
|
+
for item in locale_result['issues']:
|
|
220
|
+
print(f" {item}")
|
|
221
|
+
|
|
222
|
+
print("\n[CODE ANALYSIS]")
|
|
223
|
+
print("-" * 40)
|
|
224
|
+
for item in code_result['passed']:
|
|
225
|
+
print(f" {item}")
|
|
226
|
+
for item in code_result['issues']:
|
|
227
|
+
print(f" {item}")
|
|
228
|
+
|
|
229
|
+
# Summary
|
|
230
|
+
critical_issues = sum(1 for i in locale_result['issues'] + code_result['issues'] if i.startswith("[X]"))
|
|
231
|
+
|
|
232
|
+
print("\n" + "=" * 60)
|
|
233
|
+
if critical_issues == 0:
|
|
234
|
+
print("[OK] i18n CHECK: PASSED")
|
|
235
|
+
sys.exit(0)
|
|
236
|
+
else:
|
|
237
|
+
print(f"[X] i18n CHECK: {critical_issues} issues found")
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
|
|
240
|
+
if __name__ == "__main__":
|
|
241
|
+
main()
|