secure-coding-rules 2.0.0 → 2.0.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/README.md +71 -60
- package/package.json +5 -5
- package/src/__tests__/adapters.test.js +201 -0
- package/src/__tests__/loader.test.js +68 -0
- package/src/adapters/agents.js +2 -2
- package/src/adapters/claude.js +42 -3
- package/src/adapters/copilot.js +46 -2
- package/src/i18n.js +271 -0
- package/src/index.js +222 -56
- package/src/loader.js +33 -20
- package/src/prompts.js +79 -77
- package/src/templates/frameworks/express-security.md +130 -0
- package/src/templates/frameworks/nextjs-security.md +149 -0
- package/src/templates/frameworks/react-security.md +120 -0
- package/src/templates/typescript/typescript-security.md +113 -0
package/src/adapters/copilot.js
CHANGED
|
@@ -6,17 +6,18 @@ import { getCategoryInfo } from '../loader.js';
|
|
|
6
6
|
|
|
7
7
|
export const name = 'GitHub Copilot';
|
|
8
8
|
export const outputPath = '.github/copilot-instructions.md';
|
|
9
|
+
export const rulesDir = '.github/instructions';
|
|
9
10
|
export const description = 'Generates .github/copilot-instructions.md';
|
|
10
11
|
|
|
11
12
|
const SECTION_START = '<!-- js-secure-coding:start -->';
|
|
12
13
|
const SECTION_END = '<!-- js-secure-coding:end -->';
|
|
13
14
|
|
|
14
15
|
export function format(templates, options = {}) {
|
|
15
|
-
const { framework = 'vanilla' } = options;
|
|
16
|
+
const { framework = 'vanilla', version = '2.0.0' } = options;
|
|
16
17
|
const lines = [];
|
|
17
18
|
|
|
18
19
|
lines.push(SECTION_START);
|
|
19
|
-
lines.push(
|
|
20
|
+
lines.push(`<!-- version: ${version} -->`);
|
|
20
21
|
lines.push('');
|
|
21
22
|
lines.push('## Security Coding Guidelines (OWASP 2025)');
|
|
22
23
|
lines.push('');
|
|
@@ -54,6 +55,49 @@ export function merge(existingContent, newSection) {
|
|
|
54
55
|
return (trimmed ? trimmed + '\n\n' : '') + newSection + '\n';
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Format templates into individual files for directory mode
|
|
60
|
+
* Returns Map<filename, content>
|
|
61
|
+
*/
|
|
62
|
+
export function formatMultiple(templates, options = {}) {
|
|
63
|
+
const files = new Map();
|
|
64
|
+
for (const [category, content] of templates) {
|
|
65
|
+
const info = getCategoryInfo(category);
|
|
66
|
+
const lines = [
|
|
67
|
+
`# ${info.owasp}: ${info.title}`,
|
|
68
|
+
'',
|
|
69
|
+
`> OWASP 2025 Security Rule | Generated by secure-coding-rules`,
|
|
70
|
+
'',
|
|
71
|
+
content.replace(/^# .+\n+(?:>.*\n+)*/, ''),
|
|
72
|
+
];
|
|
73
|
+
files.set(`security-${category}.md`, lines.join('\n'));
|
|
74
|
+
}
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate reference text for copilot-instructions.md pointing to rules directory
|
|
80
|
+
*/
|
|
81
|
+
export function formatReference(categories, options = {}) {
|
|
82
|
+
const { version = '2.0.0' } = options;
|
|
83
|
+
const lines = [
|
|
84
|
+
SECTION_START,
|
|
85
|
+
`<!-- version: ${version} -->`,
|
|
86
|
+
'',
|
|
87
|
+
'## Security Coding Guidelines (OWASP 2025)',
|
|
88
|
+
'',
|
|
89
|
+
'Detailed security rules are in `.github/instructions/security-*.md`.',
|
|
90
|
+
'',
|
|
91
|
+
];
|
|
92
|
+
for (const cat of categories) {
|
|
93
|
+
const info = getCategoryInfo(cat);
|
|
94
|
+
lines.push(`- \`security-${cat}.md\` - ${info.owasp}: ${info.title}`);
|
|
95
|
+
}
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push(SECTION_END);
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
57
101
|
function extractRulesOnly(content) {
|
|
58
102
|
const lines = content.split('\n');
|
|
59
103
|
const result = [];
|
package/src/i18n.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight i18n - zero dependencies
|
|
3
|
+
* Auto-detects system locale, supports --lang flag override
|
|
4
|
+
* Supported: en, ko, ja, zh
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const messages = {
|
|
8
|
+
en: {
|
|
9
|
+
title: 'Secure Coding Rules - OWASP 2025 Security Rules Generator',
|
|
10
|
+
helpDesc: 'Apply OWASP 2025 JavaScript security rules to your AI coding assistant',
|
|
11
|
+
|
|
12
|
+
selectTool: 'Which AI coding tool do you use?',
|
|
13
|
+
selectTools: 'Which AI coding tools do you use?',
|
|
14
|
+
selectFramework: 'What is your primary framework?',
|
|
15
|
+
includeAll: 'Include all OWASP 2025 security categories?',
|
|
16
|
+
selectCategories: 'Select security categories:',
|
|
17
|
+
includeFrontend: 'Include frontend-specific security rules?',
|
|
18
|
+
selectPrompt: 'Select',
|
|
19
|
+
selectAll: 'All',
|
|
20
|
+
selectAllComma: 'comma-separated, e.g. 1,3,5 or 0 for all',
|
|
21
|
+
invalidSelection: 'Invalid selection, defaulting to first option.',
|
|
22
|
+
noValidSelection: 'No valid selection, selecting all.',
|
|
23
|
+
selectOutputMode: 'Where should security rules be placed?',
|
|
24
|
+
outputInline: 'Inline (embed in main file)',
|
|
25
|
+
outputDirectory: 'Directory (separate rule files + reference in main file)',
|
|
26
|
+
|
|
27
|
+
projectStatus: 'Project Status:',
|
|
28
|
+
toolsDetected: 'AI tools detected:',
|
|
29
|
+
noToolsFound: 'No AI tool configs found (new setup)',
|
|
30
|
+
frameworkDetected: 'Framework detected:',
|
|
31
|
+
noPackageJson: 'No package.json found (rules will be created in current directory)',
|
|
32
|
+
existingRules: 'Existing rules found - will update security section only.',
|
|
33
|
+
nonInteractive: 'Non-interactive environment detected, using defaults.',
|
|
34
|
+
detected: 'detected',
|
|
35
|
+
|
|
36
|
+
loading: 'Loading security templates...',
|
|
37
|
+
loaded: (n) => `Loaded ${n} security rule modules.`,
|
|
38
|
+
noTemplates: 'No templates found. Please check your installation.',
|
|
39
|
+
created: (f) => `Created: ${f}`,
|
|
40
|
+
updated: (f) => `Updated: ${f} (merged with existing content)`,
|
|
41
|
+
generated: (n, d) => `Generated ${n} files in ${d}/`,
|
|
42
|
+
generatingFor: (tool) => `Generating for ${tool}...`,
|
|
43
|
+
refUpdated: (f) => `Updated: ${f} (added rules directory reference)`,
|
|
44
|
+
refCreated: (f) => `Created: ${f} (rules directory reference)`,
|
|
45
|
+
success: 'Security rules generated successfully!',
|
|
46
|
+
reference: 'Based on OWASP Top 10 2025 (https://owasp.org/Top10/2025/)',
|
|
47
|
+
runAgain: 'Run again anytime to update: npx secure-coding-rules',
|
|
48
|
+
|
|
49
|
+
removedMarkers: (f) => `Cleaned security section from ${f}`,
|
|
50
|
+
removedFile: (f) => `Removed ${f} (was security-only)`,
|
|
51
|
+
removedDir: (n, d) => `Removed ${n} security rule files from ${d}/`,
|
|
52
|
+
noRulesFound: 'No security rules found to remove.',
|
|
53
|
+
removeSuccess: 'All security rules removed.',
|
|
54
|
+
|
|
55
|
+
dryRunTitle: 'Dry Run Preview',
|
|
56
|
+
dryRunFramework: 'Framework:',
|
|
57
|
+
dryRunCategories: 'Categories:',
|
|
58
|
+
dryRunWouldGenerate: (n, d) => `Would generate ${n} files in ${d}/:`,
|
|
59
|
+
dryRunWouldCreate: (f) => `Would create: ${f}`,
|
|
60
|
+
dryRunWouldUpdate: (f) => `Would update: ${f}`,
|
|
61
|
+
dryRunSize: (s) => `Content size: ${s} KB`,
|
|
62
|
+
dryRunApply: 'Run without --dry-run to apply',
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
ko: {
|
|
66
|
+
title: 'Secure Coding Rules - OWASP 2025 보안 룰 생성기',
|
|
67
|
+
helpDesc: 'OWASP 2025 기반 JavaScript 보안 코딩 룰을 AI 코딩 어시스턴트에 자동 적용',
|
|
68
|
+
|
|
69
|
+
selectTool: '사용 중인 AI 코딩 도구를 선택하세요:',
|
|
70
|
+
selectTools: '사용할 AI 코딩 도구를 선택하세요:',
|
|
71
|
+
selectFramework: '주요 프레임워크를 선택하세요:',
|
|
72
|
+
includeAll: '모든 OWASP 2025 보안 카테고리를 포함할까요?',
|
|
73
|
+
selectCategories: '보안 카테고리를 선택하세요:',
|
|
74
|
+
includeFrontend: '프론트엔드 보안 룰을 포함할까요?',
|
|
75
|
+
selectPrompt: '선택',
|
|
76
|
+
selectAll: '전체',
|
|
77
|
+
selectAllComma: '쉼표로 구분, 예: 1,3,5 또는 0으로 전체 선택',
|
|
78
|
+
invalidSelection: '잘못된 선택입니다. 첫 번째 항목으로 설정합니다.',
|
|
79
|
+
noValidSelection: '유효한 선택 없음. 전체 선택합니다.',
|
|
80
|
+
selectOutputMode: '보안 룰을 어디에 배치할까요?',
|
|
81
|
+
outputInline: '인라인 (메인 파일에 직접 삽입)',
|
|
82
|
+
outputDirectory: '디렉토리 (룰 파일 분리 + 메인 파일에 참조)',
|
|
83
|
+
|
|
84
|
+
projectStatus: '프로젝트 상태:',
|
|
85
|
+
toolsDetected: '감지된 AI 도구:',
|
|
86
|
+
noToolsFound: 'AI 도구 설정 없음 (신규 설정)',
|
|
87
|
+
frameworkDetected: '감지된 프레임워크:',
|
|
88
|
+
noPackageJson: 'package.json 없음 (현재 디렉토리에 룰 생성)',
|
|
89
|
+
existingRules: '기존 룰 발견 - 보안 섹션만 업데이트합니다.',
|
|
90
|
+
nonInteractive: '비대화형 환경 감지, 기본값 적용.',
|
|
91
|
+
detected: '감지됨',
|
|
92
|
+
|
|
93
|
+
loading: '보안 템플릿 로딩 중...',
|
|
94
|
+
loaded: (n) => `${n}개 보안 룰 모듈 로드 완료.`,
|
|
95
|
+
noTemplates: '템플릿을 찾을 수 없습니다. 설치를 확인해주세요.',
|
|
96
|
+
created: (f) => `생성됨: ${f}`,
|
|
97
|
+
updated: (f) => `업데이트됨: ${f} (기존 내용과 병합)`,
|
|
98
|
+
generated: (n, d) => `${d}/에 ${n}개 파일 생성됨`,
|
|
99
|
+
generatingFor: (tool) => `${tool} 생성 중...`,
|
|
100
|
+
refUpdated: (f) => `업데이트됨: ${f} (룰 디렉토리 참조 추가)`,
|
|
101
|
+
refCreated: (f) => `생성됨: ${f} (룰 디렉토리 참조)`,
|
|
102
|
+
success: '보안 룰이 성공적으로 생성되었습니다!',
|
|
103
|
+
reference: 'OWASP Top 10 2025 기반 (https://owasp.org/Top10/2025/)',
|
|
104
|
+
runAgain: '업데이트하려면 다시 실행: npx secure-coding-rules',
|
|
105
|
+
|
|
106
|
+
removedMarkers: (f) => `${f}에서 보안 섹션 제거됨`,
|
|
107
|
+
removedFile: (f) => `${f} 삭제됨 (보안 전용 파일)`,
|
|
108
|
+
removedDir: (n, d) => `${d}/에서 보안 룰 파일 ${n}개 삭제됨`,
|
|
109
|
+
noRulesFound: '제거할 보안 룰이 없습니다.',
|
|
110
|
+
removeSuccess: '모든 보안 룰이 제거되었습니다.',
|
|
111
|
+
|
|
112
|
+
dryRunTitle: '미리보기 (Dry Run)',
|
|
113
|
+
dryRunFramework: '프레임워크:',
|
|
114
|
+
dryRunCategories: '카테고리:',
|
|
115
|
+
dryRunWouldGenerate: (n, d) => `${d}/에 ${n}개 파일 생성 예정:`,
|
|
116
|
+
dryRunWouldCreate: (f) => `생성 예정: ${f}`,
|
|
117
|
+
dryRunWouldUpdate: (f) => `업데이트 예정: ${f}`,
|
|
118
|
+
dryRunSize: (s) => `콘텐츠 크기: ${s} KB`,
|
|
119
|
+
dryRunApply: '--dry-run 없이 실행하면 적용됩니다',
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
ja: {
|
|
123
|
+
title: 'Secure Coding Rules - OWASP 2025 セキュリティルール生成ツール',
|
|
124
|
+
helpDesc: 'OWASP 2025準拠のJavaScriptセキュリティルールをAIコーディングアシスタントに自動適用',
|
|
125
|
+
|
|
126
|
+
selectTools: '使用するAIコーディングツールを選択してください:',
|
|
127
|
+
selectFramework: 'メインフレームワークを選択してください:',
|
|
128
|
+
includeAll: 'すべてのOWASP 2025セキュリティカテゴリを含めますか?',
|
|
129
|
+
selectCategories: 'セキュリティカテゴリを選択:',
|
|
130
|
+
includeFrontend: 'フロントエンドセキュリティルールを含めますか?',
|
|
131
|
+
selectPrompt: '選択',
|
|
132
|
+
selectAll: 'すべて',
|
|
133
|
+
selectAllComma: 'カンマ区切り、例: 1,3,5 または 0で全選択',
|
|
134
|
+
invalidSelection: '無効な選択です。最初のオプションを使用します。',
|
|
135
|
+
noValidSelection: '有効な選択がありません。すべて選択します。',
|
|
136
|
+
selectOutputMode: 'セキュリティルールの配置先は?',
|
|
137
|
+
outputInline: 'インライン(メインファイルに直接埋め込み)',
|
|
138
|
+
outputDirectory: 'ディレクトリ(ルールファイル分離+メインファイルに参照)',
|
|
139
|
+
|
|
140
|
+
projectStatus: 'プロジェクト状態:',
|
|
141
|
+
toolsDetected: '検出されたAIツール:',
|
|
142
|
+
noToolsFound: 'AIツール設定なし(新規セットアップ)',
|
|
143
|
+
frameworkDetected: '検出されたフレームワーク:',
|
|
144
|
+
noPackageJson: 'package.jsonなし(カレントディレクトリにルール作成)',
|
|
145
|
+
existingRules: '既存ルール検出 - セキュリティセクションのみ更新します。',
|
|
146
|
+
nonInteractive: '非対話環境を検出、デフォルト値を適用。',
|
|
147
|
+
detected: '検出済み',
|
|
148
|
+
|
|
149
|
+
loading: 'セキュリティテンプレートを読み込み中...',
|
|
150
|
+
loaded: (n) => `${n}個のセキュリティルールモジュールを読み込みました。`,
|
|
151
|
+
noTemplates: 'テンプレートが見つかりません。インストールを確認してください。',
|
|
152
|
+
created: (f) => `作成: ${f}`,
|
|
153
|
+
updated: (f) => `更新: ${f}(既存コンテンツとマージ)`,
|
|
154
|
+
generated: (n, d) => `${d}/に${n}個のファイルを生成`,
|
|
155
|
+
generatingFor: (tool) => `${tool}を生成中...`,
|
|
156
|
+
refUpdated: (f) => `更新: ${f}(ルールディレクトリ参照を追加)`,
|
|
157
|
+
refCreated: (f) => `作成: ${f}(ルールディレクトリ参照)`,
|
|
158
|
+
success: 'セキュリティルールが正常に生成されました!',
|
|
159
|
+
reference: 'OWASP Top 10 2025準拠 (https://owasp.org/Top10/2025/)',
|
|
160
|
+
runAgain: '更新するには再実行: npx secure-coding-rules',
|
|
161
|
+
|
|
162
|
+
removedMarkers: (f) => `${f}からセキュリティセクションを削除`,
|
|
163
|
+
removedFile: (f) => `${f}を削除(セキュリティ専用ファイル)`,
|
|
164
|
+
removedDir: (n, d) => `${d}/から${n}個のセキュリティルールファイルを削除`,
|
|
165
|
+
noRulesFound: '削除するセキュリティルールが見つかりません。',
|
|
166
|
+
removeSuccess: 'すべてのセキュリティルールを削除しました。',
|
|
167
|
+
|
|
168
|
+
dryRunTitle: 'プレビュー (Dry Run)',
|
|
169
|
+
dryRunFramework: 'フレームワーク:',
|
|
170
|
+
dryRunCategories: 'カテゴリ:',
|
|
171
|
+
dryRunWouldGenerate: (n, d) => `${d}/に${n}個のファイルを生成予定:`,
|
|
172
|
+
dryRunWouldCreate: (f) => `作成予定: ${f}`,
|
|
173
|
+
dryRunWouldUpdate: (f) => `更新予定: ${f}`,
|
|
174
|
+
dryRunSize: (s) => `コンテンツサイズ: ${s} KB`,
|
|
175
|
+
dryRunApply: '--dry-runなしで実行すると適用されます',
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
zh: {
|
|
179
|
+
title: 'Secure Coding Rules - OWASP 2025 安全规则生成器',
|
|
180
|
+
helpDesc: '将OWASP 2025 JavaScript安全编码规则自动应用到AI编码助手',
|
|
181
|
+
|
|
182
|
+
selectTools: '请选择使用的AI编码工具:',
|
|
183
|
+
selectFramework: '请选择主要框架:',
|
|
184
|
+
includeAll: '是否包含所有OWASP 2025安全类别?',
|
|
185
|
+
selectCategories: '选择安全类别:',
|
|
186
|
+
includeFrontend: '是否包含前端安全规则?',
|
|
187
|
+
selectPrompt: '选择',
|
|
188
|
+
selectAll: '全部',
|
|
189
|
+
selectAllComma: '逗号分隔,例: 1,3,5 或 0选择全部',
|
|
190
|
+
invalidSelection: '无效选择,使用第一个选项。',
|
|
191
|
+
noValidSelection: '无有效选择,选择全部。',
|
|
192
|
+
selectOutputMode: '安全规则放置在哪里?',
|
|
193
|
+
outputInline: '内联(嵌入主文件)',
|
|
194
|
+
outputDirectory: '目录(规则文件分离 + 主文件中引用)',
|
|
195
|
+
|
|
196
|
+
projectStatus: '项目状态:',
|
|
197
|
+
toolsDetected: '检测到的AI工具:',
|
|
198
|
+
noToolsFound: '未找到AI工具配置(新设置)',
|
|
199
|
+
frameworkDetected: '检测到的框架:',
|
|
200
|
+
noPackageJson: '未找到package.json(将在当前目录创建规则)',
|
|
201
|
+
existingRules: '发现现有规则 - 仅更新安全部分。',
|
|
202
|
+
nonInteractive: '检测到非交互环境,使用默认值。',
|
|
203
|
+
detected: '已检测',
|
|
204
|
+
|
|
205
|
+
loading: '正在加载安全模板...',
|
|
206
|
+
loaded: (n) => `已加载 ${n} 个安全规则模块。`,
|
|
207
|
+
noTemplates: '未找到模板。请检查安装。',
|
|
208
|
+
created: (f) => `已创建: ${f}`,
|
|
209
|
+
updated: (f) => `已更新: ${f}(与现有内容合并)`,
|
|
210
|
+
generated: (n, d) => `在 ${d}/ 中生成了 ${n} 个文件`,
|
|
211
|
+
generatingFor: (tool) => `正在为 ${tool} 生成...`,
|
|
212
|
+
refUpdated: (f) => `已更新: ${f}(添加了规则目录引用)`,
|
|
213
|
+
refCreated: (f) => `已创建: ${f}(规则目录引用)`,
|
|
214
|
+
success: '安全规则生成成功!',
|
|
215
|
+
reference: '基于 OWASP Top 10 2025 (https://owasp.org/Top10/2025/)',
|
|
216
|
+
runAgain: '随时再次运行以更新: npx secure-coding-rules',
|
|
217
|
+
|
|
218
|
+
removedMarkers: (f) => `已从 ${f} 中清除安全部分`,
|
|
219
|
+
removedFile: (f) => `已删除 ${f}(仅含安全内容)`,
|
|
220
|
+
removedDir: (n, d) => `已从 ${d}/ 中删除 ${n} 个安全规则文件`,
|
|
221
|
+
noRulesFound: '未找到可删除的安全规则。',
|
|
222
|
+
removeSuccess: '所有安全规则已删除。',
|
|
223
|
+
|
|
224
|
+
dryRunTitle: '预览 (Dry Run)',
|
|
225
|
+
dryRunFramework: '框架:',
|
|
226
|
+
dryRunCategories: '类别:',
|
|
227
|
+
dryRunWouldGenerate: (n, d) => `将在 ${d}/ 中生成 ${n} 个文件:`,
|
|
228
|
+
dryRunWouldCreate: (f) => `将创建: ${f}`,
|
|
229
|
+
dryRunWouldUpdate: (f) => `将更新: ${f}`,
|
|
230
|
+
dryRunSize: (s) => `内容大小: ${s} KB`,
|
|
231
|
+
dryRunApply: '不加 --dry-run 运行以应用',
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
let currentLang = 'en';
|
|
236
|
+
|
|
237
|
+
export function initLang(args = []) {
|
|
238
|
+
const langIdx = args.indexOf('--lang');
|
|
239
|
+
if (langIdx !== -1 && args[langIdx + 1]) {
|
|
240
|
+
const requested = args[langIdx + 1].toLowerCase();
|
|
241
|
+
if (messages[requested]) {
|
|
242
|
+
currentLang = requested;
|
|
243
|
+
return currentLang;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const locale = (
|
|
248
|
+
process.env.LANG ||
|
|
249
|
+
process.env.LC_ALL ||
|
|
250
|
+
process.env.LC_MESSAGES ||
|
|
251
|
+
''
|
|
252
|
+
).toLowerCase();
|
|
253
|
+
|
|
254
|
+
if (locale.startsWith('ko')) currentLang = 'ko';
|
|
255
|
+
else if (locale.startsWith('ja')) currentLang = 'ja';
|
|
256
|
+
else if (locale.startsWith('zh')) currentLang = 'zh';
|
|
257
|
+
else currentLang = 'en';
|
|
258
|
+
|
|
259
|
+
return currentLang;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function t(key, ...args) {
|
|
263
|
+
const msg = messages[currentLang]?.[key] || messages.en[key] || key;
|
|
264
|
+
return typeof msg === 'function' ? msg(...args) : msg;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function getLang() {
|
|
268
|
+
return currentLang;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const supportedLangs = Object.keys(messages);
|