diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ba6bdd6..0949b7f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/public/sw.js b/public/sw.js index 8f4bdbfd..ae4d49ae 100644 --- a/public/sw.js +++ b/public/sw.js @@ -28,6 +28,20 @@ const STATIC_ASSETS = [ ...OFFLINE_FALLBACK_CANDIDATES, ]; +const SENSITIVE_QUERY_PARAMS = [ + 'handoff', + 'handoff_ref', + 'token', + 'secret', + 'payload', + 'key', + 'authorization', +]; + +function hasSensitiveQuery(url) { + return SENSITIVE_QUERY_PARAMS.some((param) => url.searchParams.has(param)); +} + function matchOfflineFallback() { return caches.match(OFFLINE_FALLBACK_URL) .then((cached) => cached || caches.match('/offline')) @@ -79,6 +93,8 @@ self.addEventListener('fetch', (event) => { if (event.request.method !== 'GET') return; // Do not cache or intercept third-party requests. if (url.origin !== self.location.origin) return; + // Keep tool payloads and secret-like query strings out of CacheStorage. + if (hasSensitiveQuery(url)) return; // Network-first for Next.js chunks and pages (ensures fresh code) if ( diff --git a/src/core/codegen/literals.ts b/src/core/codegen/literals.ts new file mode 100644 index 00000000..d01f69dd --- /dev/null +++ b/src/core/codegen/literals.ts @@ -0,0 +1,82 @@ +export function shellSingleQuote(value: string): string { + if (value.length === 0) return "''" + return `'${value.replace(/'/g, "'\\''")}'` +} + +export function jsStringLiteral(value: string): string { + return JSON.stringify(value) +} + +export function pythonStringLiteral(value: string): string { + return JSON.stringify(value) +} + +export function goStringLiteral(value: string): string { + return JSON.stringify(value) +} + +export function phpStringLiteral(value: string): string { + return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'` +} + +function rustEscapedStringLiteral(value: string): string { + let output = '"' + for (const char of value) { + switch (char) { + case "\\": + output += "\\\\" + break + case '"': + output += '\\"' + break + case "\n": + output += "\\n" + break + case "\r": + output += "\\r" + break + case "\t": + output += "\\t" + break + case "\0": + output += "\\0" + break + default: { + const codePoint = char.codePointAt(0) ?? 0 + output += codePoint < 0x20 + ? `\\u{${codePoint.toString(16)}}` + : char + } + } + } + return `${output}"` +} + +export function rustStringLiteral(value: string): string { + for (let hashCount = 0; hashCount <= 8; hashCount += 1) { + const hashes = "#".repeat(hashCount) + const endDelimiter = `"${hashes}` + if (!value.includes(endDelimiter)) { + return `r${hashes}"${value}"${hashes}` + } + } + + return rustEscapedStringLiteral(value) +} + +export function jsJsonBodyExpression(value: string): string | null { + try { + return `JSON.stringify(${JSON.stringify(JSON.parse(value), null, 2)})` + } catch { + return null + } +} + +export function pythonJsonBodyExpression(value: string): string | null { + try { + JSON.parse(value) + return `json.loads(${pythonStringLiteral(value)})` + } catch { + return null + } +} diff --git a/src/core/i18n/translations/de.json b/src/core/i18n/translations/de.json index 6ce49011..6636802a 100644 --- a/src/core/i18n/translations/de.json +++ b/src/core/i18n/translations/de.json @@ -761,7 +761,8 @@ "strength_weak": "Schwach", "strength_fair": "Mittel", "strength_good": "Gut", - "strength_strong": "Stark" + "strength_strong": "Stark", + "crypto_unavailable": "Web Crypto ist erforderlich, um sichere Passwoerter zu generieren." }, "color_converter": { "title": "Farbkonverter", diff --git a/src/core/i18n/translations/en.json b/src/core/i18n/translations/en.json index f0297168..e99f237e 100644 --- a/src/core/i18n/translations/en.json +++ b/src/core/i18n/translations/en.json @@ -781,7 +781,8 @@ "strength_weak": "Weak", "strength_fair": "Fair", "strength_good": "Good", - "strength_strong": "Strong" + "strength_strong": "Strong", + "crypto_unavailable": "Web Crypto is required to generate secure passwords." }, "color_converter": { "title": "Color Converter", diff --git a/src/core/i18n/translations/fr.json b/src/core/i18n/translations/fr.json index 2359f494..4d7199a3 100644 --- a/src/core/i18n/translations/fr.json +++ b/src/core/i18n/translations/fr.json @@ -761,7 +761,8 @@ "strength_weak": "Faible", "strength_fair": "Moyen", "strength_good": "Bon", - "strength_strong": "Fort" + "strength_strong": "Fort", + "crypto_unavailable": "Web Crypto est requis pour generer des mots de passe securises." }, "color_converter": { "title": "Convertisseur de couleurs", diff --git a/src/core/i18n/translations/ja.json b/src/core/i18n/translations/ja.json index e9607f8d..2185e1b7 100644 --- a/src/core/i18n/translations/ja.json +++ b/src/core/i18n/translations/ja.json @@ -761,7 +761,8 @@ "strength_weak": "弱い", "strength_fair": "普通", "strength_good": "良い", - "strength_strong": "強い" + "strength_strong": "強い", + "crypto_unavailable": "安全なパスワード生成には Web Crypto が必要です。" }, "color_converter": { "title": "カラー変換", diff --git a/src/core/i18n/translations/ko.json b/src/core/i18n/translations/ko.json index ba36b785..9789d9ab 100644 --- a/src/core/i18n/translations/ko.json +++ b/src/core/i18n/translations/ko.json @@ -761,7 +761,8 @@ "strength_weak": "약함", "strength_fair": "보통", "strength_good": "좋음", - "strength_strong": "강함" + "strength_strong": "강함", + "crypto_unavailable": "안전한 비밀번호를 생성하려면 Web Crypto가 필요합니다." }, "color_converter": { "title": "색상 변환기", diff --git a/src/core/i18n/translations/zh-CN.json b/src/core/i18n/translations/zh-CN.json index e8f3a630..1cf3620b 100644 --- a/src/core/i18n/translations/zh-CN.json +++ b/src/core/i18n/translations/zh-CN.json @@ -761,7 +761,8 @@ "strength_weak": "弱", "strength_fair": "一般", "strength_good": "良好", - "strength_strong": "强" + "strength_strong": "强", + "crypto_unavailable": "需要 Web Crypto 才能生成安全密码。" }, "color_converter": { "title": "颜色转换器", diff --git a/src/core/i18n/translations/zh-TW.json b/src/core/i18n/translations/zh-TW.json index 2e76ea89..5b7ab9d6 100644 --- a/src/core/i18n/translations/zh-TW.json +++ b/src/core/i18n/translations/zh-TW.json @@ -761,7 +761,8 @@ "strength_weak": "弱", "strength_fair": "一般", "strength_good": "良好", - "strength_strong": "強" + "strength_strong": "強", + "crypto_unavailable": "需要 Web Crypto 才能產生安全密碼。" }, "color_converter": { "title": "顏色轉換器", diff --git a/src/features/tools/curl-to-code/logic.ts b/src/features/tools/curl-to-code/logic.ts new file mode 100644 index 00000000..c161e49d --- /dev/null +++ b/src/features/tools/curl-to-code/logic.ts @@ -0,0 +1,214 @@ +import { + goStringLiteral, + jsJsonBodyExpression, + jsStringLiteral, + phpStringLiteral, + pythonJsonBodyExpression, + pythonStringLiteral, + rustStringLiteral, +} from "@/core/codegen/literals" + +export type OutputLang = "javascript" | "python" | "go" | "php" | "rust" + +export interface ParsedRequest { + method: string + url: string + headers: Record + data: string + dataType: "raw" | "json" | "form" +} + +export function parseCurl(curlStr: string): ParsedRequest | null { + try { + const result: ParsedRequest = { method: "GET", url: "", headers: {}, data: "", dataType: "raw" } + + const normalized = curlStr.replace(/\\\n\s*/g, " ").replace(/\\\r?\n\s*/g, " ").trim() + const rest = normalized.replace(/^curl\s+/, "") + const urlMatch = rest.match(/['"]?(https?:\/\/[^\s'"]+)['"]?/) + if (urlMatch) { + result.url = urlMatch[1] + } + + const methodMatch = rest.match(/-X\s+['"]?(\w+)['"]?/i) + if (methodMatch) { + result.method = methodMatch[1].toUpperCase() + } + + const headerRegex = /-H\s+['"]([^'"]+)['"]/g + let headerMatch: RegExpExecArray | null + while ((headerMatch = headerRegex.exec(rest)) !== null) { + const colonIdx = headerMatch[1].indexOf(":") + if (colonIdx > 0) { + const key = headerMatch[1].slice(0, colonIdx).trim() + const value = headerMatch[1].slice(colonIdx + 1).trim() + result.headers[key] = value + } + } + + const dataMatch = rest.match(/(?:-d|--data|--data-raw|--data-binary)\s+['"]([^'"]*)['"]/i) || + rest.match(/(?:-d|--data|--data-raw|--data-binary)\s+(\S+)/i) + if (dataMatch) { + result.data = dataMatch[1] + if (result.method === "GET") result.method = "POST" + + try { + JSON.parse(result.data) + result.dataType = "json" + } catch { + if (result.data.includes("=") && !result.data.includes("{")) { + result.dataType = "form" + } + } + } + + if (!result.url) return null + return result + } catch { + return null + } +} + +export function toJavaScript(req: ParsedRequest): string { + const lines: string[] = [] + const hasHeaders = Object.keys(req.headers).length > 0 + const hasBody = !!req.data + + lines.push(`const response = await fetch(${jsStringLiteral(req.url)}, {`) + lines.push(` method: ${jsStringLiteral(req.method)},`) + if (hasHeaders) { + lines.push(` headers: ${JSON.stringify(req.headers, null, 2).replace(/\n/g, "\n ")},`) + } + if (hasBody) { + const jsonExpression = req.dataType === "json" ? jsJsonBodyExpression(req.data) : null + lines.push(` body: ${jsonExpression ?? jsStringLiteral(req.data)},`) + } + lines.push("});") + lines.push("") + lines.push("const data = await response.json();") + lines.push("console.log(data);") + return lines.join("\n") +} + +export function toPython(req: ParsedRequest): string { + const lines: string[] = ["import requests"] + const hasHeaders = Object.keys(req.headers).length > 0 + const method = req.method.toLowerCase() + const args = [pythonStringLiteral(req.url)] + + if (hasHeaders) { + lines.push("", `headers = ${JSON.stringify(req.headers, null, 4)}`) + args.push("headers=headers") + } + + if (req.data) { + if (req.dataType === "json") { + const payloadExpression = pythonJsonBodyExpression(req.data) + if (payloadExpression) { + lines[0] = "import json" + lines.push("", `payload = ${payloadExpression}`) + args.push("json=payload") + } else { + lines.push("", `data = ${pythonStringLiteral(req.data)}`) + args.push("data=data") + } + } else { + lines.push("", `data = ${pythonStringLiteral(req.data)}`) + args.push("data=data") + } + } + + lines.push("") + lines.push(`response = requests.${method}(${args.join(", ")})`) + lines.push("print(response.status_code)") + lines.push("print(response.json())") + return lines.join("\n") +} + +export function toGo(req: ParsedRequest): string { + const lines: string[] = [ + "package main", "", "import (", " \"fmt\"", " \"io\"", " \"net/http\"", + ] + if (req.data) lines.push(" \"strings\"") + lines.push(")", "") + lines.push("func main() {") + + if (req.data) { + lines.push(` body := strings.NewReader(${goStringLiteral(req.data)})`) + lines.push(` req, err := http.NewRequest(${goStringLiteral(req.method)}, ${goStringLiteral(req.url)}, body)`) + } else { + lines.push(` req, err := http.NewRequest(${goStringLiteral(req.method)}, ${goStringLiteral(req.url)}, nil)`) + } + lines.push(" if err != nil { panic(err) }") + + for (const [key, value] of Object.entries(req.headers)) { + lines.push(` req.Header.Set(${goStringLiteral(key)}, ${goStringLiteral(value)})`) + } + + lines.push(" resp, err := http.DefaultClient.Do(req)") + lines.push(" if err != nil { panic(err) }") + lines.push(" defer resp.Body.Close()") + lines.push(" data, _ := io.ReadAll(resp.Body)") + lines.push(" fmt.Println(string(data))") + lines.push("}") + return lines.join("\n") +} + +export function toPhp(req: ParsedRequest): string { + const lines: string[] = [" 0) { + lines.push("curl_setopt($ch, CURLOPT_HTTPHEADER, [") + for (const [key, value] of headerEntries) { + lines.push(` ${phpStringLiteral(`${key}: ${value}`)},`) + } + lines.push("]);") + } + lines.push("") + lines.push("$response = curl_exec($ch);") + lines.push("curl_close($ch);") + lines.push("echo $response;") + return lines.join("\n") +} + +export function toRust(req: ParsedRequest): string { + const lines: string[] = [ + "use reqwest;", "", "#[tokio::main]", "async fn main() -> Result<(), Box> {", + " let client = reqwest::Client::new();", "", + ] + const method = req.method.toLowerCase() + lines.push(` let response = client.${method}(${rustStringLiteral(req.url)})`) + for (const [key, value] of Object.entries(req.headers)) { + lines.push(` .header(${rustStringLiteral(key)}, ${rustStringLiteral(value)})`) + } + if (req.data) { + lines.push(` .body(${rustStringLiteral(req.data)})`) + } + lines.push(" .send()") + lines.push(" .await?;") + lines.push("") + lines.push(" println!(\"{}\", response.text().await?);") + lines.push(" Ok(())") + lines.push("}") + return lines.join("\n") +} + +export function convertCurlToCode(curl: string, lang: OutputLang, parseError: string): string { + if (!curl.trim()) return "" + const parsed = parseCurl(curl) + if (!parsed) return parseError + switch (lang) { + case "javascript": return toJavaScript(parsed) + case "python": return toPython(parsed) + case "go": return toGo(parsed) + case "php": return toPhp(parsed) + case "rust": return toRust(parsed) + } +} diff --git a/src/features/tools/curl-to-code/page.tsx b/src/features/tools/curl-to-code/page.tsx index 94411072..e8f29f65 100644 --- a/src/features/tools/curl-to-code/page.tsx +++ b/src/features/tools/curl-to-code/page.tsx @@ -15,221 +15,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" - -type OutputLang = "javascript" | "python" | "go" | "php" | "rust" - -interface ParsedRequest { - method: string - url: string - headers: Record - data: string - dataType: "raw" | "json" | "form" -} - -function parseCurl(curlStr: string): ParsedRequest | null { - try { - const result: ParsedRequest = { method: "GET", url: "", headers: {}, data: "", dataType: "raw" } - - // Normalize multiline - const normalized = curlStr.replace(/\\\n\s*/g, " ").replace(/\\\r?\n\s*/g, " ").trim() - - // Remove 'curl' prefix - const rest = normalized.replace(/^curl\s+/, "") - - // Extract URL (first unquoted token that looks like a URL, or quoted string after method) - const urlMatch = rest.match(/['"]?(https?:\/\/[^\s'"]+)['"]?/) - if (urlMatch) { - result.url = urlMatch[1] - } - - // Extract method - const methodMatch = rest.match(/-X\s+['"]?(\w+)['"]?/i) - if (methodMatch) { - result.method = methodMatch[1].toUpperCase() - } - - // Extract headers - const headerRegex = /-H\s+['"]([^'"]+)['"]/g - let hMatch - while ((hMatch = headerRegex.exec(rest)) !== null) { - const colonIdx = hMatch[1].indexOf(":") - if (colonIdx > 0) { - const key = hMatch[1].slice(0, colonIdx).trim() - const value = hMatch[1].slice(colonIdx + 1).trim() - result.headers[key] = value - } - } - - // Extract data - const dataMatch = rest.match(/(?:-d|--data|--data-raw|--data-binary)\s+['"]([^'"]*)['"]/i) || - rest.match(/(?:-d|--data|--data-raw|--data-binary)\s+(\S+)/i) - if (dataMatch) { - result.data = dataMatch[1] - if (result.method === "GET") result.method = "POST" - - // Detect type - try { - JSON.parse(result.data) - result.dataType = "json" - } catch { - if (result.data.includes("=") && !result.data.includes("{")) { - result.dataType = "form" - } - } - } - - if (!result.url) return null - return result - } catch { - return null - } -} - -function toJavaScript(req: ParsedRequest): string { - const lines: string[] = [] - const hasHeaders = Object.keys(req.headers).length > 0 - const hasBody = !!req.data - - lines.push(`const response = await fetch('${req.url}', {`) - lines.push(` method: '${req.method}',`) - if (hasHeaders) { - lines.push(` headers: {`) - for (const [k, v] of Object.entries(req.headers)) { - lines.push(` '${k}': '${v}',`) - } - lines.push(` },`) - } - if (hasBody) { - if (req.dataType === "json") { - lines.push(` body: JSON.stringify(${req.data}),`) - } else { - lines.push(` body: '${req.data}',`) - } - } - lines.push(`});`) - lines.push(``) - lines.push(`const data = await response.json();`) - lines.push(`console.log(data);`) - return lines.join("\n") -} - -function toPython(req: ParsedRequest): string { - const lines: string[] = ["import requests", ""] - const hasHeaders = Object.keys(req.headers).length > 0 - const m = req.method.toLowerCase() - - if (hasHeaders) { - lines.push(`headers = ${JSON.stringify(req.headers, null, 4)}`) - lines.push("") - } - - let args = `'${req.url}'` - if (hasHeaders) args += ", headers=headers" - if (req.data) { - if (req.dataType === "json") { - lines.push(`payload = ${req.data}`) - args += ", json=payload" - } else { - lines.push(`data = '${req.data}'`) - args += ", data=data" - } - lines.push("") - } - - lines.push(`response = requests.${m}(${args})`) - lines.push(`print(response.status_code)`) - lines.push(`print(response.json())`) - return lines.join("\n") -} - -function toGo(req: ParsedRequest): string { - const lines: string[] = [ - 'package main', '', 'import (', ' "fmt"', ' "io"', ' "net/http"', - ] - if (req.data) lines.push(' "strings"') - lines.push(')', '') - lines.push('func main() {') - - if (req.data) { - lines.push(` body := strings.NewReader(\`${req.data}\`)`) - lines.push(` req, err := http.NewRequest("${req.method}", "${req.url}", body)`) - } else { - lines.push(` req, err := http.NewRequest("${req.method}", "${req.url}", nil)`) - } - lines.push(' if err != nil { panic(err) }') - - for (const [k, v] of Object.entries(req.headers)) { - lines.push(` req.Header.Set("${k}", "${v}")`) - } - - lines.push(' resp, err := http.DefaultClient.Do(req)') - lines.push(' if err != nil { panic(err) }') - lines.push(' defer resp.Body.Close()') - lines.push(' data, _ := io.ReadAll(resp.Body)') - lines.push(' fmt.Println(string(data))') - lines.push('}') - return lines.join("\n") -} - -function toPhp(req: ParsedRequest): string { - const lines: string[] = [" 0) { - lines.push(`curl_setopt($ch, CURLOPT_HTTPHEADER, [`) - for (const [k, v] of headerEntries) { - lines.push(` '${k}: ${v}',`) - } - lines.push(`]);`) - } - lines.push("") - lines.push("$response = curl_exec($ch);") - lines.push("curl_close($ch);") - lines.push("echo $response;") - return lines.join("\n") -} - -function toRust(req: ParsedRequest): string { - const lines: string[] = [ - 'use reqwest;', '', '#[tokio::main]', 'async fn main() -> Result<(), Box> {', - ' let client = reqwest::Client::new();', '', - ] - const m = req.method.toLowerCase() - lines.push(` let response = client.${m}("${req.url}")`) - for (const [k, v] of Object.entries(req.headers)) { - lines.push(` .header("${k}", "${v}")`) - } - if (req.data) { - lines.push(` .body(r#"${req.data}"#)`) - } - lines.push(` .send()`) - lines.push(` .await?;`) - lines.push('') - lines.push(' println!("{}", response.text().await?);') - lines.push(' Ok(())') - lines.push('}') - return lines.join("\n") -} - -function convert(curl: string, lang: OutputLang, parseError: string): string { - if (!curl.trim()) return "" - const parsed = parseCurl(curl) - if (!parsed) return parseError - switch (lang) { - case "javascript": return toJavaScript(parsed) - case "python": return toPython(parsed) - case "go": return toGo(parsed) - case "php": return toPhp(parsed) - case "rust": return toRust(parsed) - } -} +import { convertCurlToCode, type OutputLang } from "./logic" const SAMPLE_CURL = `curl -X POST 'https://api.example.com/users' \\ -H 'Content-Type: application/json' \\ @@ -241,7 +27,7 @@ export function CurlToCodePage() { const toolT = t.tools["curl_to_code"] as Record const [curl, setCurl] = React.useState(SAMPLE_CURL) const [lang, setLang] = React.useState("javascript") - const output = React.useMemo(() => convert(curl, lang, toolT.parse_error), [curl, lang, toolT.parse_error]) + const output = React.useMemo(() => convertCurlToCode(curl, lang, toolT.parse_error), [curl, lang, toolT.parse_error]) const handleCopy = async () => { const result = await safeClipboardWrite(output) diff --git a/src/features/tools/http-request-builder/logic.ts b/src/features/tools/http-request-builder/logic.ts new file mode 100644 index 00000000..478deed2 --- /dev/null +++ b/src/features/tools/http-request-builder/logic.ts @@ -0,0 +1,111 @@ +import { + jsJsonBodyExpression, + jsStringLiteral, + pythonJsonBodyExpression, + pythonStringLiteral, + shellSingleQuote, +} from "@/core/codegen/literals" + +export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" +export type BodyType = "none" | "json" | "raw" | "form-urlencoded" + +export interface HeaderEntry { + id: string + key: string + value: string + enabled: boolean +} + +function enabledHeaders(headers: HeaderEntry[]): HeaderEntry[] { + return headers.filter((header) => header.enabled && header.key.trim()) +} + +function headersObject(headers: HeaderEntry[], bodyType?: BodyType): Record { + const result: Record = {} + for (const header of enabledHeaders(headers)) { + result[header.key] = header.value + } + if (bodyType === "json") result["Content-Type"] = "application/json" + if (bodyType === "form-urlencoded") result["Content-Type"] = "application/x-www-form-urlencoded" + return result +} + +export function generateCurl(method: HttpMethod, url: string, headers: HeaderEntry[], bodyType: BodyType, body: string): string { + const parts = ["curl"] + if (method !== "GET") parts.push(`-X ${method}`) + parts.push(shellSingleQuote(url)) + + for (const header of enabledHeaders(headers)) { + parts.push(`-H ${shellSingleQuote(`${header.key}: ${header.value}`)}`) + } + + if (bodyType === "json" && body) { + parts.push(`-H ${shellSingleQuote("Content-Type: application/json")}`) + parts.push(`-d ${shellSingleQuote(body)}`) + } else if (bodyType === "raw" && body) { + parts.push(`-d ${shellSingleQuote(body)}`) + } else if (bodyType === "form-urlencoded" && body) { + parts.push(`-H ${shellSingleQuote("Content-Type: application/x-www-form-urlencoded")}`) + parts.push(`--data-urlencode ${shellSingleQuote(body)}`) + } + + return parts.join(" \\\n ") +} + +export function generateFetch(method: HttpMethod, url: string, headers: HeaderEntry[], bodyType: BodyType, body: string): string { + const headersObj = headersObject(headers, bodyType) + const optionLines = [` method: ${jsStringLiteral(method)},`] + + if (Object.keys(headersObj).length > 0) { + optionLines.push(` headers: ${JSON.stringify(headersObj, null, 2).replace(/\n/g, "\n ")},`) + } + + if (body && bodyType !== "none") { + const jsonExpression = bodyType === "json" ? jsJsonBodyExpression(body) : null + optionLines.push(` body: ${jsonExpression ?? jsStringLiteral(body)},`) + } + + return [ + `const response = await fetch(${jsStringLiteral(url)}, {`, + ...optionLines, + "});", + "const data = await response.json();", + "console.log(data);", + ].join("\n") +} + +export function generatePythonRequests(method: HttpMethod, url: string, headers: HeaderEntry[], bodyType: BodyType, body: string): string { + const lines = ["import requests"] + const headersObj = headersObject(headers) + const methodLower = method.toLowerCase() + const callArgs = [pythonStringLiteral(url)] + + if (Object.keys(headersObj).length > 0) { + lines.push("", `headers = ${JSON.stringify(headersObj, null, 4)}`) + callArgs.push("headers=headers") + } + + if (body && bodyType !== "none") { + if (bodyType === "json") { + const payloadExpression = pythonJsonBodyExpression(body) + if (payloadExpression) { + lines[0] = "import json" + lines.push("", `payload = ${payloadExpression}`) + callArgs.push("json=payload") + } else { + lines.push("", `data = ${pythonStringLiteral(body)}`) + callArgs.push("data=data") + } + } else { + lines.push("", `data = ${pythonStringLiteral(body)}`) + callArgs.push("data=data") + } + } + + lines.push("") + lines.push(`response = requests.${methodLower}(${callArgs.join(", ")})`) + lines.push("print(response.status_code)") + lines.push("print(response.json())") + + return lines.join("\n") +} diff --git a/src/features/tools/http-request-builder/page.tsx b/src/features/tools/http-request-builder/page.tsx index bc73b431..8cd76ff5 100644 --- a/src/features/tools/http-request-builder/page.tsx +++ b/src/features/tools/http-request-builder/page.tsx @@ -16,16 +16,14 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" - -type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" -type BodyType = "none" | "json" | "raw" | "form-urlencoded" - -interface HeaderEntry { - id: string - key: string - value: string - enabled: boolean -} +import { + generateCurl, + generateFetch, + generatePythonRequests, + type BodyType, + type HeaderEntry, + type HttpMethod, +} from "./logic" let nextId = 0 @@ -37,90 +35,6 @@ function createDefaultHeaders(): HeaderEntry[] { return [{ id: `h_${nextId++}`, key: "Accept", value: "application/json", enabled: true }] } -// ─── Code Generators ──────────────────────────────────────────────────────── - -function generateCurl(method: HttpMethod, url: string, headers: HeaderEntry[], bodyType: BodyType, body: string): string { - const parts = ["curl"] - if (method !== "GET") parts.push(`-X ${method}`) - parts.push(`'${url}'`) - - for (const h of headers.filter((h) => h.enabled && h.key)) { - parts.push(`-H '${h.key}: ${h.value}'`) - } - - if (bodyType === "json" && body) { - parts.push(`-H 'Content-Type: application/json'`) - parts.push(`-d '${body}'`) - } else if (bodyType === "raw" && body) { - parts.push(`-d '${body}'`) - } else if (bodyType === "form-urlencoded" && body) { - parts.push(`-H 'Content-Type: application/x-www-form-urlencoded'`) - parts.push(`--data-urlencode '${body}'`) - } - - return parts.join(" \\\n ") -} - -function generateFetch(method: HttpMethod, url: string, headers: HeaderEntry[], bodyType: BodyType, body: string): string { - const headersObj: Record = {} - for (const h of headers.filter((h) => h.enabled && h.key)) { - headersObj[h.key] = h.value - } - if (bodyType === "json") headersObj["Content-Type"] = "application/json" - if (bodyType === "form-urlencoded") headersObj["Content-Type"] = "application/x-www-form-urlencoded" - - const options: Record = { method } - if (Object.keys(headersObj).length > 0) options.headers = headersObj - if (body && bodyType !== "none") { - options.body = bodyType === "json" ? `JSON.stringify(${body})` : body - } - - let code = `const response = await fetch('${url}'` - if (Object.keys(options).length > 1 || method !== "GET") { - const optStr = JSON.stringify(options, null, 2) - .replace(/"JSON\.stringify\((.*?)\)"/, "JSON.stringify($1)") - code += `, ${optStr}` - } - code += ");\n" - code += "const data = await response.json();\n" - code += "console.log(data);" - - return code -} - -function generatePythonRequests(method: HttpMethod, url: string, headers: HeaderEntry[], bodyType: BodyType, body: string): string { - const lines = ["import requests", ""] - const headersObj: Record = {} - for (const h of headers.filter((h) => h.enabled && h.key)) { - headersObj[h.key] = h.value - } - - const methodLower = method.toLowerCase() - let callArgs = `'${url}'` - - if (Object.keys(headersObj).length > 0) { - lines.push(`headers = ${JSON.stringify(headersObj, null, 4)}`) - callArgs += ", headers=headers" - } - - if (body && bodyType !== "none") { - if (bodyType === "json") { - lines.push(`payload = ${body}`) - callArgs += ", json=payload" - } else { - lines.push(`data = '${body}'`) - callArgs += ", data=data" - } - } - - lines.push("") - lines.push(`response = requests.${methodLower}(${callArgs})`) - lines.push("print(response.status_code)") - lines.push("print(response.json())") - - return lines.join("\n") -} - export function HttpRequestBuilderPage() { const { t } = useLang() const toolT = t.tools["http_request_builder"] as Record diff --git a/src/features/tools/jwt-verifier/logic.ts b/src/features/tools/jwt-verifier/logic.ts new file mode 100644 index 00000000..e4d7f5a7 --- /dev/null +++ b/src/features/tools/jwt-verifier/logic.ts @@ -0,0 +1,109 @@ +export type JwtClaimLabels = { + exp: string + nbf: string + iat: string + iss: string + sub: string + aud: string +} + +export type JwtClaimCheck = { + label: string + status: "valid" | "invalid" | "info" + value: string +} + +const MIN_UNIX_SECONDS = 0 +const MAX_UNIX_SECONDS = 253_402_300_799 // 9999-12-31T23:59:59Z + +export function base64UrlDecode(str: string): Uint8Array { + const padded = str.replace(/-/g, "+").replace(/_/g, "/") + const binary = atob(padded) + const bytes = new Uint8Array(binary.length) + for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index) + return bytes +} + +export function base64UrlEncode(bytes: Uint8Array): string { + let binary = "" + for (const byte of bytes) binary += String.fromCharCode(byte) + return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") +} + +export function decodePayload(token: string): Record | null { + try { + const parts = token.split(".") + if (parts.length !== 3) return null + const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(parts[1]))) + return payload + } catch { return null } +} + +export function decodeHeader(token: string): Record | null { + try { + const parts = token.split(".") + if (parts.length !== 3) return null + return JSON.parse(new TextDecoder().decode(base64UrlDecode(parts[0]))) + } catch { return null } +} + +export function parseUnixTimestampClaim(value: unknown): { ok: true; seconds: number; iso: string } | { ok: false; error: string } { + if (typeof value === "boolean" || value === null || Array.isArray(value) || typeof value === "object") { + return { ok: false, error: `invalid timestamp: ${String(value)}` } + } + + const seconds = typeof value === "number" ? value : Number(value) + if (!Number.isFinite(seconds)) { + return { ok: false, error: `invalid timestamp: ${String(value)}` } + } + + if (seconds < MIN_UNIX_SECONDS || seconds > MAX_UNIX_SECONDS) { + return { ok: false, error: `timestamp out of range: ${String(value)}` } + } + + const date = new Date(seconds * 1000) + if (Number.isNaN(date.getTime())) { + return { ok: false, error: `invalid timestamp: ${String(value)}` } + } + + return { ok: true, seconds, iso: date.toISOString() } +} + +function addTimestampClaimCheck( + checks: JwtClaimCheck[], + label: string, + value: unknown, + resolveStatus: (seconds: number) => JwtClaimCheck["status"], +): void { + const parsed = parseUnixTimestampClaim(value) + if (!parsed.ok) { + checks.push({ label, status: "invalid", value: parsed.error }) + return + } + checks.push({ label, status: resolveStatus(parsed.seconds), value: parsed.iso }) +} + +export function checkClaims(payload: Record, labels: JwtClaimLabels): JwtClaimCheck[] { + const checks: JwtClaimCheck[] = [] + const now = Math.floor(Date.now() / 1000) + + if (payload.exp !== undefined) { + addTimestampClaimCheck(checks, labels.exp, payload.exp, (seconds) => seconds > now ? "valid" : "invalid") + } + if (payload.nbf !== undefined) { + addTimestampClaimCheck(checks, labels.nbf, payload.nbf, (seconds) => seconds <= now ? "valid" : "invalid") + } + if (payload.iat !== undefined) { + addTimestampClaimCheck(checks, labels.iat, payload.iat, () => "info") + } + if (payload.iss !== undefined) { + checks.push({ label: labels.iss, status: "info", value: String(payload.iss) }) + } + if (payload.sub !== undefined) { + checks.push({ label: labels.sub, status: "info", value: String(payload.sub) }) + } + if (payload.aud !== undefined) { + checks.push({ label: labels.aud, status: "info", value: String(payload.aud) }) + } + return checks +} diff --git a/src/features/tools/jwt-verifier/page.tsx b/src/features/tools/jwt-verifier/page.tsx index 3f63c5fa..11891630 100644 --- a/src/features/tools/jwt-verifier/page.tsx +++ b/src/features/tools/jwt-verifier/page.tsx @@ -9,6 +9,12 @@ import { useLang } from "@/core/i18n/lang-provider" import { RelatedTools } from "@/core/seo/components/related-tools" import { ToolActionBar, type ToolAction } from "@/features/tool-shell/tool-action-bar" import { safeClipboardWrite } from "@/core/clipboard/clipboard" +import { + base64UrlEncode, + checkClaims, + decodeHeader, + decodePayload, +} from "./logic" const ICON_BUTTON_CLASS = "inline-flex h-7 w-7 items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 dark:hover:bg-accent/50" @@ -22,20 +28,6 @@ async function loadToast() { // ─── JWT Verification Engine (HMAC-SHA256 only, client-side) ──────────────── -function base64UrlDecode(str: string): Uint8Array { - const padded = str.replace(/-/g, "+").replace(/_/g, "/") - const binary = atob(padded) - const bytes = new Uint8Array(binary.length) - for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i) - return bytes -} - -function base64UrlEncode(bytes: Uint8Array): string { - let binary = "" - for (const b of bytes) binary += String.fromCharCode(b) - return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") -} - async function verifyHS256(token: string, secret: string): Promise { const parts = token.split(".") if (parts.length !== 3) return false @@ -70,59 +62,6 @@ async function verifyHS512(token: string, secret: string): Promise { return base64UrlEncode(new Uint8Array(sig)) === parts[2] } -function decodePayload(token: string): Record | null { - try { - const parts = token.split(".") - if (parts.length !== 3) return null - const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(parts[1]))) - return payload - } catch { return null } -} - -function decodeHeader(token: string): Record | null { - try { - const parts = token.split(".") - if (parts.length !== 3) return null - return JSON.parse(new TextDecoder().decode(base64UrlDecode(parts[0]))) - } catch { return null } -} - -type JwtClaimLabels = { - exp: string - nbf: string - iat: string - iss: string - sub: string - aud: string -} - -function checkClaims(payload: Record, labels: JwtClaimLabels): { label: string; status: "valid" | "invalid" | "info"; value: string }[] { - const checks: { label: string; status: "valid" | "invalid" | "info"; value: string }[] = [] - const now = Math.floor(Date.now() / 1000) - - if (payload.exp !== undefined) { - const exp = Number(payload.exp) - checks.push({ label: labels.exp, status: exp > now ? "valid" : "invalid", value: new Date(exp * 1000).toISOString() }) - } - if (payload.nbf !== undefined) { - const nbf = Number(payload.nbf) - checks.push({ label: labels.nbf, status: nbf <= now ? "valid" : "invalid", value: new Date(nbf * 1000).toISOString() }) - } - if (payload.iat !== undefined) { - checks.push({ label: labels.iat, status: "info", value: new Date(Number(payload.iat) * 1000).toISOString() }) - } - if (payload.iss !== undefined) { - checks.push({ label: labels.iss, status: "info", value: String(payload.iss) }) - } - if (payload.sub !== undefined) { - checks.push({ label: labels.sub, status: "info", value: String(payload.sub) }) - } - if (payload.aud !== undefined) { - checks.push({ label: labels.aud, status: "info", value: String(payload.aud) }) - } - return checks -} - export function JwtVerifierPage() { const { t, lang } = useLang() const toolT = t.tools["jwt_verifier"] as Record diff --git a/src/features/tools/password-generator/page.tsx b/src/features/tools/password-generator/page.tsx index 88835a48..ec8485cf 100644 --- a/src/features/tools/password-generator/page.tsx +++ b/src/features/tools/password-generator/page.tsx @@ -7,13 +7,7 @@ import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { useLang } from "@/core/i18n/lang-provider" import { RelatedTools } from "@/core/seo/components/related-tools" import { ToolActionBar, type ToolAction } from "@/features/tool-shell/tool-action-bar" @@ -36,7 +30,7 @@ export function PasswordGeneratorPage() { const { t } = useLang() const toolT = t.tools["password_generator"] as Record - const text = (key: string) => toolT[key] + const text = React.useCallback((key: string) => toolT[key], [toolT]) const [mode, setMode] = React.useState("random") const [randomOptions, setRandomOptions] = React.useState(DEFAULT_RANDOM_OPTIONS) @@ -72,13 +66,18 @@ export function PasswordGeneratorPage() { }, [savedPresets]) const regenerate = React.useCallback((count: number) => { - setResults(generatePasswordBatch({ - mode, - random: randomOptions, - passphrase: passphraseOptions, - count, - })) - }, [mode, randomOptions, passphraseOptions]) + try { + setResults(generatePasswordBatch({ + mode, + random: randomOptions, + passphrase: passphraseOptions, + count, + })) + } catch (error) { + setResults([]) + toast.error(error instanceof Error ? error.message : text("crypto_unavailable")) + } + }, [mode, passphraseOptions, randomOptions, text]) React.useEffect(() => { regenerate(batchCount) diff --git a/src/features/tools/password-generator/utils.ts b/src/features/tools/password-generator/utils.ts index 1aea4900..87009e7f 100644 --- a/src/features/tools/password-generator/utils.ts +++ b/src/features/tools/password-generator/utils.ts @@ -167,14 +167,23 @@ function getRequiredSets(options: RandomPasswordOptions): string[] { return sets.filter((set) => set.length > 0) } -function secureRandomInt(maxExclusive: number): number { +export function secureRandomInt(maxExclusive: number): number { if (maxExclusive <= 1) return 0 - if (typeof globalThis !== "undefined" && globalThis.crypto?.getRandomValues) { - const values = new Uint32Array(1) - globalThis.crypto.getRandomValues(values) - return values[0] % maxExclusive + + const crypto = globalThis.crypto + if (!crypto?.getRandomValues) { + throw new Error("Web Crypto is required to generate secure passwords.") } - return Math.floor(Math.random() * maxExclusive) + + const values = new Uint32Array(1) + const maxGeneratedValue = 0x1_0000_0000 + const rejectionLimit = maxGeneratedValue - (maxGeneratedValue % maxExclusive) + + do { + crypto.getRandomValues(values) + } while (values[0] >= rejectionLimit) + + return values[0] % maxExclusive } function pickChar(chars: string, randomInt: RandomIntFn): string { diff --git a/src/features/tools/regex-tester/page.tsx b/src/features/tools/regex-tester/page.tsx index 1b3652ee..2b086d00 100644 --- a/src/features/tools/regex-tester/page.tsx +++ b/src/features/tools/regex-tester/page.tsx @@ -29,7 +29,7 @@ export function RegexTesterPage() { setMatches(result.matches) setError(result.limited ? toolT.error_match_limit : null) } else { - setError(toolT.error_invalid_regex) + setError(result.error || toolT.error_invalid_regex) setMatches([]) } }, [flags, pattern, testString, toolT.error_invalid_regex, toolT.error_match_limit]) diff --git a/src/features/tools/regex-tester/utils.ts b/src/features/tools/regex-tester/utils.ts index b8fd361c..45f6e3c6 100644 --- a/src/features/tools/regex-tester/utils.ts +++ b/src/features/tools/regex-tester/utils.ts @@ -18,11 +18,34 @@ export type RegexTestResult = limited: false } +const MAX_PATTERN_LENGTH = 500 +const MAX_TEST_STRING_LENGTH = 20_000 +const NESTED_QUANTIFIER_RE = /\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)\s*[+*{]/ +const REPEATED_GROUP_WITH_RANGE_RE = /\((?:[^()\\]|\\.)*\{(?:\d+,?\d*|,\d+)\}(?:[^()\\]|\\.)*\)\s*[+*{]/ + +export function assessRegexSafety(pattern: string, testString: string): string | null { + if (pattern.length > MAX_PATTERN_LENGTH) { + return `Pattern exceeds the ${MAX_PATTERN_LENGTH} character safety limit.` + } + if (testString.length > MAX_TEST_STRING_LENGTH) { + return `Test string exceeds the ${MAX_TEST_STRING_LENGTH} character safety limit.` + } + if (NESTED_QUANTIFIER_RE.test(pattern) || REPEATED_GROUP_WITH_RANGE_RE.test(pattern)) { + return "Pattern contains nested quantifiers that can cause catastrophic backtracking." + } + return null +} + export function testRegexPattern(pattern: string, flags: string, testString: string, maxMatches = 5_000): RegexTestResult { if (!pattern || testString === "") { return { ok: true, matches: [], limited: false } } + const safetyError = assessRegexSafety(pattern, testString) + if (safetyError) { + return { ok: false, error: safetyError, matches: [], limited: false } + } + try { const isGlobal = flags.includes("g") const safeFlags = isGlobal ? flags : `${flags}g` diff --git a/src/features/tools/svg-optimizer/logic.ts b/src/features/tools/svg-optimizer/logic.ts new file mode 100644 index 00000000..9008b1cf --- /dev/null +++ b/src/features/tools/svg-optimizer/logic.ts @@ -0,0 +1,29 @@ +import DOMPurify from "isomorphic-dompurify" + +export function optimizeSvg(svg: string): string { + let result = svg + result = result.replace(/<\?xml[^?]*\?>\s*/gi, "") + result = result.replace(//g, "") + result = result.replace(//gi, "") + result = result.replace(//gi, "") + result = result.replace(//gi, "") + result = result.replace(/\s+(inkscape|sodipodi|xmlns:inkscape|xmlns:sodipodi|xmlns:rdf|xmlns:cc|xmlns:dc)[^=]*="[^"]*"/gi, "") + result = result.replace(/]*>\s*<\/g>/gi, "") + result = result.replace(/\s{2,}/g, " ") + result = result.replace(/>\s+<") + result = result.replace(/\s+>/g, ">") + result = result.replace(/\s+\/>/g, "/>") + return result.trim() +} + +export function sanitizeSvg(svg: string): string { + return DOMPurify.sanitize(svg, { + USE_PROFILES: { svg: true, svgFilters: true }, + FORBID_TAGS: ["script", "foreignObject", "iframe", "object", "embed"], + FORBID_ATTR: ["onload", "onclick", "onerror", "onmouseover", "onfocus", "onbegin", "onend"], + }).trim() +} + +export function optimizeAndSanitizeSvg(svg: string): string { + return sanitizeSvg(optimizeSvg(svg)) +} diff --git a/src/features/tools/svg-optimizer/page.tsx b/src/features/tools/svg-optimizer/page.tsx index c65ac5f2..0f1ca088 100644 --- a/src/features/tools/svg-optimizer/page.tsx +++ b/src/features/tools/svg-optimizer/page.tsx @@ -1,7 +1,6 @@ "use client" import * as React from "react" -import DOMPurify from "isomorphic-dompurify" import { Minimize2, BarChart3, Upload, Copy, Trash2 } from "lucide-react" import { toast } from "sonner" import { useLang } from "@/core/i18n/lang-provider" @@ -10,22 +9,7 @@ import { ToolActionBar, type ToolAction } from "@/features/tool-shell/tool-actio import { MonacoEditor } from "@/features/tool-shell/monaco-editors" import { useThemePreference } from "@/hooks/use-theme-preference" import { ensureByteflowMonacoThemes, getByteflowMonacoThemeName } from "@/core/utils/monaco-theme" - -function optimizeSvg(svg: string): string { - let result = svg - result = result.replace(/<\?xml[^?]*\?>\s*/gi, "") - result = result.replace(//g, "") - result = result.replace(//gi, "") - result = result.replace(//gi, "") - result = result.replace(//gi, "") - result = result.replace(/\s+(inkscape|sodipodi|xmlns:inkscape|xmlns:sodipodi|xmlns:rdf|xmlns:cc|xmlns:dc)[^=]*="[^"]*"/gi, "") - result = result.replace(/]*>\s*<\/g>/gi, "") - result = result.replace(/\s{2,}/g, " ") - result = result.replace(/>\s+<") - result = result.replace(/\s+>/g, ">") - result = result.replace(/\s+\/>/g, "/>") - return result.trim() -} +import { optimizeAndSanitizeSvg } from "./logic" const SAMPLE_SVG = ` @@ -59,7 +43,7 @@ export function SvgOptimizerPage() { setOutput("") return } - setOutput(optimizeSvg(input)) + setOutput(optimizeAndSanitizeSvg(input)) }, [input]) const originalSize = new Blob([input]).size @@ -174,7 +158,7 @@ export function SvgOptimizerPage() {
{output ? (
) : ( diff --git a/src/lib/sitemap-lastmod.json b/src/lib/sitemap-lastmod.json index 268a50af..082f4519 100644 --- a/src/lib/sitemap-lastmod.json +++ b/src/lib/sitemap-lastmod.json @@ -10,1539 +10,1539 @@ "fr" ], "home": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "hubs": { "formatters": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "text-tools": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "generators": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "network-tools": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "format-validate": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "convert-encode": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "text-content": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "web-api": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "generators-ids": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "design-media": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" } }, "static": { "all-tools": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "about": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "pricing": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "contact": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "privacy": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "terms": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "install-app": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "json-formatting-errors": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "json-vs-json5-differences": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "validate-json-before-api-requests": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "json-schema-validation-checklist": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "api-auth-header-mistakes": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "convert-curl-to-fetch-python": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "mock-openapi-quickly": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "openapi-debugging-workflow-checklist": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "dns-records-uptime": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "certificate-chain-basics-for-developers": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "csp-mistakes-that-break-production": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "robots-txt-testing-checklist": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-layout-patterns-for-developers": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "modern-css-effects-guide": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-animations-and-transitions-guide": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-border-radius-and-shapes-guide": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-optimization-for-web-complete-workflow": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "color-extraction-from-images-use-cases-and-tools": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "svg-optimization-and-conversion-best-practices": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-privacy-how-to-censor-and-protect-images": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "base64-encoding-when-and-how-to-use-it": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "hash-functions-compared-md5-vs-sha256-vs-sha512": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "url-encoding-explained-common-mistakes-and-solutions": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "jwt-security-best-practices-for-token-handling": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "md5-digest-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "sha1-digest-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "sha256-digest-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "sha224-digest-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "sha384-digest-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "sha512-digest-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" } }, "tools": { "json-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "xml-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "sql-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "javascript-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "javascript-minifier": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "html-minifier": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "html-encoder-decoder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "html-css-beautifier": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "html-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "yaml-json-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "markdown-preview": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "html-to-markdown": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "json-to-typescript": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-minifier": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "svg-optimizer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "jsonpath-playground": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "openapi-viewer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "json-diff-viewer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "csv-json-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "base64-encode-decode": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "url-encode-decode": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "jwt-decoder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "jwt-workbench": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "hash-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "md5-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "text-diff-checker": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "multiple-whitespace-remover": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "letter-counter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "bionic-reading-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "google-fonts-pair-finder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "text-to-handwriting-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "code-to-image-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-base64": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "unix-timestamp": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "uuid-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "lorem-ipsum": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "password-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "color-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "react-native-shadow-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "ai-color-palette-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "color-mixer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "color-shades-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-average-color-finder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-caption-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-color-extractor": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-color-picker": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-cropper": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-filters": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "instagram-filters": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "instagram-post-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "instagram-story-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "open-graph-meta-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "tweet-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "tweet-to-image-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "twitter-ad-revenue-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "instagram-photo-downloader": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "vimeo-thumbnail-grabber": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "youtube-thumbnail-grabber": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "image-resizer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "photo-censor": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "scanned-pdf-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "svg-blob-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "svg-pattern-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "svg-stroke-to-fill-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "svg-to-png-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-background-pattern-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-border-radius-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-box-shadow-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-checkbox-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-clip-path-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-cubic-bezier-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-glassmorphism-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-gradient-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-loader-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-switch-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-text-glitch-effect-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "css-triangle-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "qr-code-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "barcode-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "fake-iban-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "list-randomizer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "ascii-art-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "env-parser": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "id-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "regex-tester": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "regex-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "crontab-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "user-agent-parser": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "cron-visualizer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "http-status-codes": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "chmod-calculator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "cidr-subnet-calculator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "url-parser": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "certificate-decoder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "http-request-builder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "curl-to-code": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "ndjson-formatter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "jwt-verifier": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "slugify-case-converter": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "invisible-characters-detector": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "robots-txt-tester": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "csp-parser": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "csv-diff": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "header-diff": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "security-header-analyzer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "totp-generator": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "openapi-mock": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "docker-run-to-compose": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "local-log-parser": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "jq-playground": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "log-scrubber": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "gzip-brotli-lab": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "yaml-merge-patch-explorer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "yq-playground": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "structured-data-visualizer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "har-viewer-sanitizer": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "pipeline-builder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "saml-decoder": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "asn1-der-inspector": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "hex-bytes-workbench": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" }, "unicode-inspector": { - "en": "2026-06-17T00:00:00.000Z", - "zh-CN": "2026-06-17T00:00:00.000Z", - "zh-TW": "2026-06-17T00:00:00.000Z", - "ja": "2026-06-17T00:00:00.000Z", - "ko": "2026-06-17T00:00:00.000Z", - "de": "2026-06-17T00:00:00.000Z", - "fr": "2026-06-17T00:00:00.000Z" + "en": "2026-06-18T00:00:00.000Z", + "zh-CN": "2026-06-18T00:00:00.000Z", + "zh-TW": "2026-06-18T00:00:00.000Z", + "ja": "2026-06-18T00:00:00.000Z", + "ko": "2026-06-18T00:00:00.000Z", + "de": "2026-06-18T00:00:00.000Z", + "fr": "2026-06-18T00:00:00.000Z" } } } diff --git a/tests/guards/ci-permissions-guard.test.ts b/tests/guards/ci-permissions-guard.test.ts new file mode 100644 index 00000000..9d9bc52f --- /dev/null +++ b/tests/guards/ci-permissions-guard.test.ts @@ -0,0 +1,11 @@ +import fs from "node:fs" +import path from "node:path" +import { describe, expect, it } from "vitest" + +describe("ci permissions guard", () => { + it("keeps GitHub Actions default token permissions read-only", () => { + const source = fs.readFileSync(path.join(process.cwd(), ".github", "workflows", "ci.yml"), "utf8") + + expect(source).toContain("permissions:\n contents: read") + }) +}) diff --git a/tests/guards/html-injection-surface-guard.test.ts b/tests/guards/html-injection-surface-guard.test.ts index f8b3b9e8..40a6873e 100644 --- a/tests/guards/html-injection-surface-guard.test.ts +++ b/tests/guards/html-injection-surface-guard.test.ts @@ -59,11 +59,16 @@ describe("HTML injection surface guard", () => { }) it("sanitizes user-provided SVG output before previewing it as HTML", () => { - const source = readSource("src/features/tools/svg-optimizer/page.tsx") - - expect(source).toContain("import DOMPurify from \"isomorphic-dompurify\"") - expect(source).toContain("dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(output) }}") - expect(countMatches(source, /dangerouslySetInnerHTML=\{\{/g)).toBe(1) + const pageSource = readSource("src/features/tools/svg-optimizer/page.tsx") + const logicSource = readSource("src/features/tools/svg-optimizer/logic.ts") + + expect(logicSource).toContain("import DOMPurify from \"isomorphic-dompurify\"") + expect(logicSource).toContain("export function sanitizeSvg(svg: string): string") + expect(logicSource).toContain("DOMPurify.sanitize(svg") + expect(logicSource).toContain("FORBID_TAGS") + expect(pageSource).toContain("setOutput(optimizeAndSanitizeSvg(input))") + expect(pageSource).toContain("dangerouslySetInnerHTML={{ __html: output }}") + expect(countMatches(pageSource, /dangerouslySetInnerHTML=\{\{/g)).toBe(1) }) it("embeds dynamic redirect and runtime script data with JSON serialization only", () => { diff --git a/tests/guards/offline-fallback-page.test.ts b/tests/guards/offline-fallback-page.test.ts index a75f1f05..7538ba90 100644 --- a/tests/guards/offline-fallback-page.test.ts +++ b/tests/guards/offline-fallback-page.test.ts @@ -21,4 +21,15 @@ describe("offline fallback page", () => { expect(source).toContain("new Response(") expect(source).toContain("You are offline") }) + + it("keeps tool payload query strings out of CacheStorage", () => { + const source = fs.readFileSync(path.join(process.cwd(), "public", "sw.js"), "utf8") + + expect(source).toContain("const SENSITIVE_QUERY_PARAMS = [") + expect(source).toContain("'handoff'") + expect(source).toContain("'handoff_ref'") + expect(source).toContain("'secret'") + expect(source).toContain("function hasSensitiveQuery(url)") + expect(source).toContain("if (hasSensitiveQuery(url)) return;") + }) }) diff --git a/tests/unit/codegen-literals.test.ts b/tests/unit/codegen-literals.test.ts new file mode 100644 index 00000000..79cb4842 --- /dev/null +++ b/tests/unit/codegen-literals.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest" +import { + goStringLiteral, + jsStringLiteral, + phpStringLiteral, + pythonStringLiteral, + rustStringLiteral, + shellSingleQuote, +} from "@/core/codegen/literals" + +describe("codegen literals", () => { + it("escapes POSIX shell single-quoted arguments", () => { + expect(shellSingleQuote("x' ; echo INJECTED ; #")).toBe("'x'\\'' ; echo INJECTED ; #'") + }) + + it("uses structured language string literals", () => { + const value = "line1\n'\"\\${x}`\0" + + expect(JSON.parse(jsStringLiteral(value))).toBe(value) + expect(JSON.parse(pythonStringLiteral(value))).toBe(value) + expect(JSON.parse(goStringLiteral(value))).toBe(value) + expect(phpStringLiteral("x'\\y")).toBe("'x\\'\\\\y'") + }) + + it("chooses a Rust raw string delimiter that cannot be closed by the input", () => { + const literal = rustStringLiteral("body\"#; injected(); //") + + expect(literal).toBe('r##"body"#; injected(); //"##') + }) +}) diff --git a/tests/unit/curl-to-code-codegen.test.ts b/tests/unit/curl-to-code-codegen.test.ts new file mode 100644 index 00000000..74fb62f0 --- /dev/null +++ b/tests/unit/curl-to-code-codegen.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest" +import { + convertCurlToCode, + toGo, + toJavaScript, + toPhp, + toPython, + toRust, +} from "@/features/tools/curl-to-code/logic" + +const MALICIOUS_CURL = `curl -X POST 'https://api.example.com/x'; console.log('JS_INJECTED'); //'' \\ + -H 'X-Test': 'v'; print('PY_INJECTED') #' \\ + -d 'x' ; echo BODY_INJECTED ; #''` + +describe("curl to code codegen", () => { + it("does not emit attacker-controlled URL as JavaScript syntax", () => { + const code = convertCurlToCode(MALICIOUS_CURL, "javascript", "parse error") + + expect(code).toContain(`fetch(${JSON.stringify("https://api.example.com/x")}, {`) + expect(code).not.toContain("fetch('https://api.example.com/x';") + }) + + it("does not emit attacker-controlled body as Python syntax", () => { + const code = convertCurlToCode(MALICIOUS_CURL, "python", "parse error") + + expect(code).toContain(`data = ${JSON.stringify("x")}`) + expect(code).not.toContain("data = 'x' ; echo BODY_INJECTED") + }) + + it("uses safe string literal encoders across generated languages", () => { + const parsed = { + method: "POST", + url: "https://api.example.com/x\"; injected(); //", + headers: { "X-Test": "v\"; injected(); //" }, + data: "body\"#; injected(); //", + dataType: "raw" as const, + } + + expect(toJavaScript(parsed)).toContain(`fetch(${JSON.stringify(parsed.url)}, {`) + expect(toPython(parsed)).toContain(`requests.post(${JSON.stringify(parsed.url)}`) + expect(toGo(parsed)).toContain(`http.NewRequest("POST", ${JSON.stringify(parsed.url)}, body)`) + expect(toPhp(parsed)).toContain("curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/x\"; injected(); //');") + expect(toRust(parsed)).toContain('client.post(r#"https://api.example.com/x"; injected(); //"#)') + expect(toRust(parsed)).toContain('body(r##"body"#; injected(); //"##)') + }) +}) diff --git a/tests/unit/http-request-builder-codegen.test.ts b/tests/unit/http-request-builder-codegen.test.ts new file mode 100644 index 00000000..596740e1 --- /dev/null +++ b/tests/unit/http-request-builder-codegen.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest" +import { + generateCurl, + generateFetch, + generatePythonRequests, + type HeaderEntry, +} from "@/features/tools/http-request-builder/logic" + +const maliciousHeaders: HeaderEntry[] = [ + { + id: "h1", + key: "X-Test'\nconsole.log('HEADER_INJECTED')//", + value: "v' ; echo SHELL_INJECTED ; #'", + enabled: true, + }, +] + +const maliciousUrl = "https://api.example.com/x'; console.log('JS_INJECTED'); //'" +const maliciousBody = "x' ; echo BODY_INJECTED ; #'" + +describe("http request builder codegen", () => { + it("escapes generated shell arguments", () => { + const curl = generateCurl("POST", maliciousUrl, maliciousHeaders, "raw", maliciousBody) + + expect(curl).toContain("'https://api.example.com/x'\\''; console.log('\\''JS_INJECTED'\\''); //'\\'''") + expect(curl).toContain("-d 'x'\\'' ; echo BODY_INJECTED ; #'\\'''") + expect(curl).not.toContain(`'${maliciousBody}'`) + }) + + it("uses JavaScript string literals instead of executable interpolation", () => { + const code = generateFetch("POST", maliciousUrl, maliciousHeaders, "raw", maliciousBody) + + expect(code).toContain(`fetch(${JSON.stringify(maliciousUrl)}, {`) + expect(code).toContain(`body: ${JSON.stringify(maliciousBody)}`) + expect(code).not.toContain("fetch('https://api.example.com/x';") + }) + + it("uses Python string literals for URL and body", () => { + const code = generatePythonRequests("POST", maliciousUrl, maliciousHeaders, "raw", maliciousBody) + + expect(code).toContain(`data = ${JSON.stringify(maliciousBody)}`) + expect(code).toContain(`response = requests.post(${JSON.stringify(maliciousUrl)}, headers=headers, data=data)`) + expect(code).not.toContain("data = 'x' ; echo BODY_INJECTED") + }) +}) diff --git a/tests/unit/jwt-verifier-claims.test.ts b/tests/unit/jwt-verifier-claims.test.ts new file mode 100644 index 00000000..aafa72bb --- /dev/null +++ b/tests/unit/jwt-verifier-claims.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest" +import { checkClaims, parseUnixTimestampClaim, type JwtClaimLabels } from "@/features/tools/jwt-verifier/logic" + +const labels: JwtClaimLabels = { + exp: "exp", + nbf: "nbf", + iat: "iat", + iss: "iss", + sub: "sub", + aud: "aud", +} + +describe("jwt verifier claims", () => { + it("rejects non-finite timestamp claims without throwing", () => { + expect(parseUnixTimestampClaim("not-a-number")).toEqual({ + ok: false, + error: "invalid timestamp: not-a-number", + }) + expect(parseUnixTimestampClaim(Number.POSITIVE_INFINITY)).toEqual({ + ok: false, + error: "invalid timestamp: Infinity", + }) + }) + + it("marks invalid timestamp claims as invalid checks", () => { + const checks = checkClaims({ exp: "not-a-number", nbf: {}, iat: 253_402_300_800 }, labels) + + expect(checks).toEqual([ + { label: "exp", status: "invalid", value: "invalid timestamp: not-a-number" }, + { label: "nbf", status: "invalid", value: "invalid timestamp: [object Object]" }, + { label: "iat", status: "invalid", value: "timestamp out of range: 253402300800" }, + ]) + }) + + it("formats valid timestamp claims", () => { + const parsed = parseUnixTimestampClaim(1_704_067_200) + + expect(parsed).toEqual({ + ok: true, + seconds: 1_704_067_200, + iso: "2024-01-01T00:00:00.000Z", + }) + }) +}) diff --git a/tests/unit/password-utils.test.ts b/tests/unit/password-utils.test.ts index d89dcbae..6f89caab 100644 --- a/tests/unit/password-utils.test.ts +++ b/tests/unit/password-utils.test.ts @@ -6,6 +6,7 @@ import { generatePassphrase, generatePasswordBatch, generateRandomPassword, + secureRandomInt, } from "@/features/tools/password-generator/utils" const zeroRandom = () => 0 @@ -90,4 +91,45 @@ describe("password-utils", () => { expect(high.entropy).toBeGreaterThan(low.entropy) expect(high.fraction).toBeGreaterThanOrEqual(low.fraction) }) + + it("fails closed when Web Crypto is unavailable", () => { + const originalCrypto = globalThis.crypto + Object.defineProperty(globalThis, "crypto", { + configurable: true, + value: undefined, + }) + + try { + expect(() => secureRandomInt(10)).toThrow("Web Crypto is required") + } finally { + Object.defineProperty(globalThis, "crypto", { + configurable: true, + value: originalCrypto, + }) + } + }) + + it("uses rejection sampling instead of modulo-biased random values", () => { + const originalCrypto = globalThis.crypto + const maxExclusive = 10 + const values = [4_294_967_295, 12] + Object.defineProperty(globalThis, "crypto", { + configurable: true, + value: { + getRandomValues(array: Uint32Array) { + array[0] = values.shift() ?? 0 + return array + }, + }, + }) + + try { + expect(secureRandomInt(maxExclusive)).toBe(2) + } finally { + Object.defineProperty(globalThis, "crypto", { + configurable: true, + value: originalCrypto, + }) + } + }) }) diff --git a/tests/unit/regex-tester-utils.test.ts b/tests/unit/regex-tester-utils.test.ts index 3fc7c43f..936ee198 100644 --- a/tests/unit/regex-tester-utils.test.ts +++ b/tests/unit/regex-tester-utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest" -import { testRegexPattern } from "@/features/tools/regex-tester/utils" +import { assessRegexSafety, testRegexPattern } from "@/features/tools/regex-tester/utils" describe("regex tester utils", () => { it("returns match summaries with captures", () => { @@ -22,4 +22,22 @@ describe("regex tester utils", () => { expect(result.error).toMatch(/Invalid regular expression/) } }) + + it("blocks nested quantifier patterns before running them", () => { + const pattern = "^(a+)+$" + const input = `${"a".repeat(30)}X` + + expect(assessRegexSafety(pattern, input)).toMatch(/nested quantifiers/) + const result = testRegexPattern(pattern, "g", input) + + expect(result.ok).toBe(false) + if (!result.ok) { + expect(result.error).toMatch(/catastrophic backtracking/) + } + }) + + it("enforces pattern and input safety limits", () => { + expect(testRegexPattern("a".repeat(501), "g", "aaa").ok).toBe(false) + expect(testRegexPattern("a", "g", "a".repeat(20_001)).ok).toBe(false) + }) }) diff --git a/tests/unit/svg-optimizer-sanitizer.test.ts b/tests/unit/svg-optimizer-sanitizer.test.ts new file mode 100644 index 00000000..3741af01 --- /dev/null +++ b/tests/unit/svg-optimizer-sanitizer.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest" +import { optimizeAndSanitizeSvg } from "@/features/tools/svg-optimizer/logic" + +describe("svg optimizer sanitizer", () => { + it("removes active SVG content from default output", () => { + const output = optimizeAndSanitizeSvg(` + + + + + +
x
+
+ `) + + expect(output).toContain("