Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
408
scripts/docs-i18n/localized_links.go
Normal file
408
scripts/docs-i18n/localized_links.go
Normal file
@@ -0,0 +1,408 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type routeIndex struct {
|
||||
targetLang string
|
||||
redirects map[string]string
|
||||
sourceRoutes map[string]struct{}
|
||||
localizedRoutes map[string]struct{}
|
||||
localePrefixes map[string]struct{}
|
||||
}
|
||||
|
||||
type docsConfig struct {
|
||||
Redirects []docsRedirect `json:"redirects"`
|
||||
}
|
||||
|
||||
type docsRedirect struct {
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
}
|
||||
|
||||
var (
|
||||
localeDirRe = regexp.MustCompile(`^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})?$`)
|
||||
fencedBacktickCodeBlock = regexp.MustCompile("(?ms)(^|\\n)[ \\t]*```[^\\n]*\\n.*?\\n[ \\t]*```[ \\t]*(?:\\n|$)")
|
||||
fencedTildeCodeBlock = regexp.MustCompile("(?ms)(^|\\n)[ \\t]*~~~[^\\n]*\\n.*?\\n[ \\t]*~~~[ \\t]*(?:\\n|$)")
|
||||
markdownLinkTargetRe = regexp.MustCompile(`!?\[[^\]]*\]\(([^)]+)\)`)
|
||||
hrefDoubleQuotedValueRe = regexp.MustCompile(`\bhref\s*=\s*"([^"]*)"`)
|
||||
hrefSingleQuotedValueRe = regexp.MustCompile(`\bhref\s*=\s*'([^']*)'`)
|
||||
)
|
||||
|
||||
func loadRouteIndex(docsRoot, targetLang string) (*routeIndex, error) {
|
||||
index := &routeIndex{
|
||||
targetLang: strings.TrimSpace(targetLang),
|
||||
redirects: map[string]string{},
|
||||
sourceRoutes: map[string]struct{}{},
|
||||
localizedRoutes: map[string]struct{}{},
|
||||
localePrefixes: map[string]struct{}{},
|
||||
}
|
||||
|
||||
if err := index.loadRedirects(filepath.Join(docsRoot, "docs.json")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := index.loadRoutes(docsRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (ri *routeIndex) loadRedirects(configPath string) error {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var config docsConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range config.Redirects {
|
||||
source := normalizeRoute(item.Source)
|
||||
destination := normalizeRoute(item.Destination)
|
||||
if source == "" || destination == "" {
|
||||
continue
|
||||
}
|
||||
ri.redirects[source] = destination
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ri *routeIndex) loadRoutes(docsRoot string) error {
|
||||
localePrefixes, err := discoverLocalePrefixes(docsRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ri.targetLang != "" {
|
||||
localePrefixes[ri.targetLang] = struct{}{}
|
||||
}
|
||||
ri.localePrefixes = localePrefixes
|
||||
|
||||
return filepath.WalkDir(docsRoot, func(path string, entry os.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if entry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !isMarkdownFile(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(docsRoot, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath = normalizeSlashes(relPath)
|
||||
firstSegment := firstPathSegment(relPath)
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
permalinks := extractPermalinks(content)
|
||||
|
||||
switch {
|
||||
case firstSegment == ri.targetLang:
|
||||
trimmedRel := strings.TrimPrefix(relPath, firstSegment+"/")
|
||||
addRouteCandidates(ri.localizedRoutes, trimmedRel, permalinks)
|
||||
case ri.isLocalePrefix(firstSegment):
|
||||
return nil
|
||||
default:
|
||||
addRouteCandidates(ri.sourceRoutes, relPath, permalinks)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func discoverLocalePrefixes(docsRoot string) (map[string]struct{}, error) {
|
||||
entries, err := os.ReadDir(docsRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locales := map[string]struct{}{}
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !localeDirRe.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(docsRoot, name, ".i18n", "README.md")); err != nil {
|
||||
continue
|
||||
}
|
||||
locales[name] = struct{}{}
|
||||
}
|
||||
return locales, nil
|
||||
}
|
||||
|
||||
func isMarkdownFile(path string) bool {
|
||||
return strings.HasSuffix(path, ".md") || strings.HasSuffix(path, ".mdx")
|
||||
}
|
||||
|
||||
func normalizeSlashes(path string) string {
|
||||
return strings.ReplaceAll(path, "\\", "/")
|
||||
}
|
||||
|
||||
func firstPathSegment(relPath string) string {
|
||||
if relPath == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.SplitN(relPath, "/", 2)
|
||||
return parts[0]
|
||||
}
|
||||
|
||||
func addRouteCandidates(routes map[string]struct{}, relPath string, permalinks []string) {
|
||||
base := strings.TrimSuffix(strings.TrimSuffix(relPath, ".md"), ".mdx")
|
||||
if base != relPath {
|
||||
addRoute(routes, normalizeRoute(base))
|
||||
switch {
|
||||
case base == "index":
|
||||
addRoute(routes, "/")
|
||||
case strings.HasSuffix(base, "/index"):
|
||||
addRoute(routes, normalizeRoute(strings.TrimSuffix(base, "/index")))
|
||||
}
|
||||
}
|
||||
|
||||
for _, permalink := range permalinks {
|
||||
addRoute(routes, normalizeRoute(permalink))
|
||||
}
|
||||
}
|
||||
|
||||
func addRoute(routes map[string]struct{}, route string) {
|
||||
if route == "" {
|
||||
return
|
||||
}
|
||||
routes[route] = struct{}{}
|
||||
}
|
||||
|
||||
func extractPermalinks(content []byte) []string {
|
||||
frontMatter, _ := splitFrontMatter(string(content))
|
||||
if strings.TrimSpace(frontMatter) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := map[string]any{}
|
||||
if err := yaml.Unmarshal([]byte(frontMatter), &data); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, ok := data["permalink"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
permalink := strings.TrimSpace(raw)
|
||||
if permalink == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{permalink}
|
||||
}
|
||||
|
||||
func normalizeRoute(path string) string {
|
||||
trimmed := strings.TrimSpace(path)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
stripped := strings.Trim(trimmed, "/")
|
||||
if stripped == "" {
|
||||
return "/"
|
||||
}
|
||||
return "/" + stripped
|
||||
}
|
||||
|
||||
func (ri *routeIndex) localizeBodyLinks(body string) string {
|
||||
if ri == nil || ri.targetLang == "" || strings.EqualFold(ri.targetLang, "en") {
|
||||
return body
|
||||
}
|
||||
|
||||
state := NewPlaceholderState(body)
|
||||
placeholders := make([]string, 0, 8)
|
||||
mapping := map[string]string{}
|
||||
masked := maskMatches(body, fencedBacktickCodeBlock, state.Next, &placeholders, mapping)
|
||||
masked = maskMatches(masked, fencedTildeCodeBlock, state.Next, &placeholders, mapping)
|
||||
masked = maskMatches(masked, inlineCodeRe, state.Next, &placeholders, mapping)
|
||||
|
||||
masked = rewriteMarkdownLinkTargets(masked, ri)
|
||||
masked = rewriteHrefTargets(masked, ri)
|
||||
|
||||
return unmaskMarkdown(masked, placeholders, mapping)
|
||||
}
|
||||
|
||||
func rewriteMarkdownLinkTargets(text string, ri *routeIndex) string {
|
||||
matches := markdownLinkTargetRe.FindAllStringSubmatchIndex(text, -1)
|
||||
if len(matches) == 0 {
|
||||
return text
|
||||
}
|
||||
|
||||
var out strings.Builder
|
||||
pos := 0
|
||||
for _, span := range matches {
|
||||
fullStart, targetStart, targetEnd := span[0], span[2], span[3]
|
||||
if fullStart < pos {
|
||||
continue
|
||||
}
|
||||
|
||||
out.WriteString(text[pos:targetStart])
|
||||
target := text[targetStart:targetEnd]
|
||||
if text[fullStart] == '!' {
|
||||
out.WriteString(target)
|
||||
} else {
|
||||
out.WriteString(ri.localizeURL(target))
|
||||
}
|
||||
pos = targetEnd
|
||||
}
|
||||
out.WriteString(text[pos:])
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func rewriteHrefTargets(text string, ri *routeIndex) string {
|
||||
text = rewriteCapturedTargets(text, hrefDoubleQuotedValueRe, 2, ri)
|
||||
text = rewriteCapturedTargets(text, hrefSingleQuotedValueRe, 2, ri)
|
||||
return text
|
||||
}
|
||||
|
||||
func rewriteCapturedTargets(text string, re *regexp.Regexp, groupIndex int, ri *routeIndex) string {
|
||||
matches := re.FindAllStringSubmatchIndex(text, -1)
|
||||
if len(matches) == 0 {
|
||||
return text
|
||||
}
|
||||
|
||||
var out strings.Builder
|
||||
pos := 0
|
||||
for _, span := range matches {
|
||||
start, end := span[groupIndex], span[groupIndex+1]
|
||||
if start < pos || start < 0 || end < 0 {
|
||||
continue
|
||||
}
|
||||
out.WriteString(text[pos:start])
|
||||
out.WriteString(ri.localizeURL(text[start:end]))
|
||||
pos = end
|
||||
}
|
||||
out.WriteString(text[pos:])
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func (ri *routeIndex) localizeURL(raw string) string {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return raw
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, "//") {
|
||||
return raw
|
||||
}
|
||||
if hasURLScheme(trimmed) {
|
||||
return raw
|
||||
}
|
||||
|
||||
pathPart, suffix := splitURLSuffix(trimmed)
|
||||
if !strings.HasPrefix(pathPart, "/") {
|
||||
return raw
|
||||
}
|
||||
|
||||
normalized := normalizeRoute(pathPart)
|
||||
if ri.routeHasLocalePrefix(normalized) {
|
||||
return raw
|
||||
}
|
||||
|
||||
canonical, ok := ri.resolveRoute(normalized)
|
||||
if !ok {
|
||||
return raw
|
||||
}
|
||||
if _, ok := ri.localizedRoutes[canonical]; !ok {
|
||||
return raw
|
||||
}
|
||||
|
||||
return prefixLocaleRoute(ri.targetLang, canonical) + suffix
|
||||
}
|
||||
|
||||
func hasURLScheme(raw string) bool {
|
||||
switch {
|
||||
case hasSchemePrefix(raw, "http://"), hasSchemePrefix(raw, "https://"):
|
||||
return true
|
||||
case hasSchemePrefix(raw, "mailto:"), hasSchemePrefix(raw, "tel:"):
|
||||
return true
|
||||
case hasSchemePrefix(raw, "data:"), hasSchemePrefix(raw, "javascript:"), hasSchemePrefix(raw, "vbscript:"):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hasSchemePrefix(raw, prefix string) bool {
|
||||
if len(raw) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(raw[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func splitURLSuffix(raw string) (string, string) {
|
||||
index := strings.IndexAny(raw, "?#")
|
||||
if index == -1 {
|
||||
return raw, ""
|
||||
}
|
||||
return raw[:index], raw[index:]
|
||||
}
|
||||
|
||||
func prefixLocaleRoute(lang, route string) string {
|
||||
if route == "/" {
|
||||
return "/" + lang
|
||||
}
|
||||
return "/" + lang + route
|
||||
}
|
||||
|
||||
func (ri *routeIndex) routeHasLocalePrefix(route string) bool {
|
||||
if route == "/" {
|
||||
return false
|
||||
}
|
||||
firstSegment := strings.TrimPrefix(route, "/")
|
||||
firstSegment = strings.SplitN(firstSegment, "/", 2)[0]
|
||||
return ri.isLocalePrefix(firstSegment)
|
||||
}
|
||||
|
||||
func (ri *routeIndex) isLocalePrefix(segment string) bool {
|
||||
if segment == "" {
|
||||
return false
|
||||
}
|
||||
_, ok := ri.localePrefixes[segment]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ri *routeIndex) resolveRoute(route string) (string, bool) {
|
||||
current := normalizeRoute(route)
|
||||
if current == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
seen := map[string]struct{}{current: {}}
|
||||
for {
|
||||
next, ok := ri.redirects[current]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
current = next
|
||||
if _, ok := seen[current]; ok {
|
||||
return "", false
|
||||
}
|
||||
seen[current] = struct{}{}
|
||||
}
|
||||
|
||||
if current == "/" {
|
||||
_, ok := ri.localizedRoutes[current]
|
||||
return current, ok
|
||||
}
|
||||
if _, ok := ri.sourceRoutes[current]; ok {
|
||||
return current, true
|
||||
}
|
||||
if _, ok := ri.localizedRoutes[current]; ok {
|
||||
return current, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
Reference in New Issue
Block a user