Compare commits
2 Commits
upstream/u
...
v0.12.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e22614648 | ||
|
|
5d87e1e563 |
95
.github/workflows/build-and-upload.yml
vendored
95
.github/workflows/build-and-upload.yml
vendored
@@ -28,6 +28,21 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
|
upload_actions_artifacts:
|
||||||
|
description: "Upload built artifacts to GitHub Actions run artifacts"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
actions_artifacts_retention_days:
|
||||||
|
description: "Retention (days) for GitHub Actions artifacts"
|
||||||
|
required: false
|
||||||
|
default: 7
|
||||||
|
type: number
|
||||||
|
actions_artifacts_name_prefix:
|
||||||
|
description: "Optional prefix for Actions artifact names"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
type: string
|
||||||
set_versions:
|
set_versions:
|
||||||
description: "Run npm version to set workspace versions"
|
description: "Run npm version to set workspace versions"
|
||||||
required: false
|
required: false
|
||||||
@@ -203,6 +218,15 @@ jobs:
|
|||||||
gh release upload "$TAG" "$file" --clobber
|
gh release upload "$TAG" "$file" --clobber
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Electron macOS)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-macos
|
||||||
|
path: packages/electron-app/release/*.zip
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
runs-on: windows-2025
|
runs-on: windows-2025
|
||||||
env:
|
env:
|
||||||
@@ -244,6 +268,15 @@ jobs:
|
|||||||
gh release upload $env:TAG $_.FullName --clobber
|
gh release upload $env:TAG $_.FullName --clobber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Electron Windows)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-windows
|
||||||
|
path: packages/electron-app/release/*.zip
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
env:
|
env:
|
||||||
@@ -286,6 +319,15 @@ jobs:
|
|||||||
gh release upload "$TAG" "$file" --clobber
|
gh release upload "$TAG" "$file" --clobber
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Electron Linux)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-linux
|
||||||
|
path: packages/electron-app/release/*.zip
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
build-tauri-macos:
|
build-tauri-macos:
|
||||||
runs-on: macos-15-intel
|
runs-on: macos-15-intel
|
||||||
env:
|
env:
|
||||||
@@ -339,7 +381,7 @@ jobs:
|
|||||||
run: npm exec -- tauri build
|
run: npm exec -- tauri build
|
||||||
|
|
||||||
- name: Package Tauri artifacts (macOS)
|
- name: Package Tauri artifacts (macOS)
|
||||||
if: ${{ inputs.upload }}
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
BUNDLE_ROOT="packages/tauri-app/target/release/bundle"
|
BUNDLE_ROOT="packages/tauri-app/target/release/bundle"
|
||||||
@@ -350,6 +392,15 @@ jobs:
|
|||||||
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip"
|
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Tauri macOS)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-macos
|
||||||
|
path: packages/tauri-app/release-tauri/*.zip
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload Tauri release assets (macOS)
|
- name: Upload Tauri release assets (macOS)
|
||||||
if: ${{ inputs.upload && inputs.tag != '' }}
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
||||||
run: |
|
run: |
|
||||||
@@ -414,7 +465,7 @@ jobs:
|
|||||||
run: npm exec -- tauri build
|
run: npm exec -- tauri build
|
||||||
|
|
||||||
- name: Package Tauri artifacts (macOS arm64)
|
- name: Package Tauri artifacts (macOS arm64)
|
||||||
if: ${{ inputs.upload }}
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
BUNDLE_ROOT="packages/tauri-app/target/release/bundle"
|
BUNDLE_ROOT="packages/tauri-app/target/release/bundle"
|
||||||
@@ -425,6 +476,15 @@ jobs:
|
|||||||
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip"
|
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Tauri macOS arm64)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-macos-arm64
|
||||||
|
path: packages/tauri-app/release-tauri/*.zip
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload Tauri release assets (macOS arm64)
|
- name: Upload Tauri release assets (macOS arm64)
|
||||||
if: ${{ inputs.upload && inputs.tag != '' }}
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
||||||
run: |
|
run: |
|
||||||
@@ -492,7 +552,7 @@ jobs:
|
|||||||
run: npm exec -- tauri build
|
run: npm exec -- tauri build
|
||||||
|
|
||||||
- name: Package Tauri artifacts (Windows)
|
- name: Package Tauri artifacts (Windows)
|
||||||
if: ${{ inputs.upload }}
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$bundleRoot = "packages/tauri-app/target/release/bundle"
|
$bundleRoot = "packages/tauri-app/target/release/bundle"
|
||||||
@@ -505,6 +565,15 @@ jobs:
|
|||||||
Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force
|
Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Tauri Windows)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-windows
|
||||||
|
path: packages/tauri-app/release-tauri/*.zip
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload Tauri release assets (Windows)
|
- name: Upload Tauri release assets (Windows)
|
||||||
if: ${{ inputs.upload && inputs.tag != '' }}
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -582,7 +651,7 @@ jobs:
|
|||||||
run: npm exec -- tauri build
|
run: npm exec -- tauri build
|
||||||
|
|
||||||
- name: Package Tauri artifacts (Linux)
|
- name: Package Tauri artifacts (Linux)
|
||||||
if: ${{ inputs.upload }}
|
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SEARCH_ROOT="packages/tauri-app/target"
|
SEARCH_ROOT="packages/tauri-app/target"
|
||||||
@@ -608,6 +677,15 @@ jobs:
|
|||||||
cp "$deb" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.deb"
|
cp "$deb" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.deb"
|
||||||
cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.rpm"
|
cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.rpm"
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Tauri Linux)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}tauri-linux
|
||||||
|
path: packages/tauri-app/release-tauri/*
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload Tauri release assets (Linux)
|
- name: Upload Tauri release assets (Linux)
|
||||||
if: ${{ inputs.upload && inputs.tag != '' }}
|
if: ${{ inputs.upload && inputs.tag != '' }}
|
||||||
run: |
|
run: |
|
||||||
@@ -766,3 +844,12 @@ jobs:
|
|||||||
echo "Uploading $file"
|
echo "Uploading $file"
|
||||||
gh release upload "$TAG" "$file" --clobber
|
gh release upload "$TAG" "$file" --clobber
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Upload Actions artifacts (Electron Linux RPM)
|
||||||
|
if: ${{ inputs.upload_actions_artifacts }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.actions_artifacts_name_prefix }}electron-linux-rpm
|
||||||
|
path: packages/electron-app/release/*.rpm
|
||||||
|
retention-days: ${{ inputs.actions_artifacts_retention_days }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|||||||
120
.github/workflows/comment-pr-artifacts.yml
vendored
Normal file
120
.github/workflows/comment-pr-artifacts.yml
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
name: Comment PR Artifacts
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows:
|
||||||
|
- PR Build Validation
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
# Only runs for PR Build Validation runs triggered by PRs.
|
||||||
|
if: ${{ github.event.workflow_run.event == 'pull_request' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Comment with artifact download link
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const run = context.payload.workflow_run;
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
|
||||||
|
const prs = run.pull_requests || [];
|
||||||
|
let prNumber = prs[0]?.number;
|
||||||
|
|
||||||
|
// `workflow_run` payload does not reliably include pull request numbers.
|
||||||
|
// Resolve PR number(s) by asking GitHub for PRs associated with the head SHA.
|
||||||
|
if (!prNumber) {
|
||||||
|
const headSha = run.head_sha;
|
||||||
|
if (!headSha) {
|
||||||
|
core.info('No PR number and no head_sha found for this workflow_run; skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const associated = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
commit_sha: headSha,
|
||||||
|
});
|
||||||
|
|
||||||
|
const open = (associated.data || []).find((p) => p.state === 'open');
|
||||||
|
prNumber = open?.number;
|
||||||
|
if (!prNumber) {
|
||||||
|
core.info(`No open PR found associated with commit ${headSha}; skipping.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only comment when the PR build job actually ran (i.e. authorization passed).
|
||||||
|
// Unauthorized PRs targeting non-dev will skip the `build` job.
|
||||||
|
const jobs = await github.paginate(
|
||||||
|
github.rest.actions.listJobsForWorkflowRun,
|
||||||
|
{ owner, repo, run_id: run.id, per_page: 100 }
|
||||||
|
);
|
||||||
|
const buildJob = jobs.find((j) => j.name === 'build');
|
||||||
|
if (!buildJob) {
|
||||||
|
core.info('No `build` job found on this run; skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (buildJob.conclusion === 'skipped') {
|
||||||
|
core.info('`build` job was skipped; skipping comment.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List artifacts from the run. If none exist (e.g. build failed before packaging),
|
||||||
|
// still comment with the run link so testers can see logs.
|
||||||
|
const artifacts = await github.paginate(
|
||||||
|
github.rest.actions.listWorkflowRunArtifacts,
|
||||||
|
{ owner, repo, run_id: run.id, per_page: 100 }
|
||||||
|
);
|
||||||
|
const active = artifacts.filter((a) => !a.expired);
|
||||||
|
|
||||||
|
const marker = '<!-- codenomad-pr-artifacts -->';
|
||||||
|
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}`;
|
||||||
|
const retentionDays = 7;
|
||||||
|
|
||||||
|
const artifactsBlock = active.length
|
||||||
|
? ['Artifacts:', ...active.map((a) => `- ${a.name}`)].join('\n')
|
||||||
|
: 'Artifacts: (none found on this run)';
|
||||||
|
|
||||||
|
const body = [
|
||||||
|
marker,
|
||||||
|
'PR builds are available as GitHub Actions artifacts:',
|
||||||
|
'',
|
||||||
|
runUrl,
|
||||||
|
'',
|
||||||
|
`Artifacts expire in ${retentionDays} days.`,
|
||||||
|
artifactsBlock,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const comments = await github.paginate(
|
||||||
|
github.rest.issues.listComments,
|
||||||
|
{ owner, repo, issue_number: prNumber, per_page: 100 }
|
||||||
|
);
|
||||||
|
const existing = comments.find((c) => (c.body || '').includes(marker));
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: existing.id,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
core.info(`Updated existing artifacts comment: ${existing.html_url}`);
|
||||||
|
} else {
|
||||||
|
const created = await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
core.info(`Created artifacts comment: ${created.data.html_url}`);
|
||||||
|
}
|
||||||
4
.github/workflows/pr-build.yml
vendored
4
.github/workflows/pr-build.yml
vendored
@@ -9,6 +9,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
actions: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: pr-build-${{ github.event.pull_request.number }}
|
group: pr-build-${{ github.event.pull_request.number }}
|
||||||
@@ -49,4 +50,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
upload: false
|
upload: false
|
||||||
|
upload_actions_artifacts: true
|
||||||
|
actions_artifacts_retention_days: 7
|
||||||
|
actions_artifacts_name_prefix: pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}-
|
||||||
set_versions: false
|
set_versions: false
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { createContext, createEffect, createMemo, createSignal, onCleanup, onMou
|
|||||||
import type { ParentComponent } from "solid-js"
|
import type { ParentComponent } from "solid-js"
|
||||||
import { useConfig } from "../../stores/preferences"
|
import { useConfig } from "../../stores/preferences"
|
||||||
import { enMessages } from "./messages/en"
|
import { enMessages } from "./messages/en"
|
||||||
|
import { esMessages } from "./messages/es"
|
||||||
|
import { frMessages } from "./messages/fr"
|
||||||
|
import { ruMessages } from "./messages/ru"
|
||||||
|
import { jaMessages } from "./messages/ja"
|
||||||
|
import { zhHansMessages } from "./messages/zh-Hans"
|
||||||
|
|
||||||
type Messages = Record<string, string>
|
type Messages = Record<string, string>
|
||||||
|
|
||||||
@@ -10,18 +15,14 @@ export type TranslateParams = Record<string, unknown>
|
|||||||
export type Locale = "en" | "es" | "fr" | "ru" | "ja" | "zh-Hans"
|
export type Locale = "en" | "es" | "fr" | "ru" | "ja" | "zh-Hans"
|
||||||
|
|
||||||
const SUPPORTED_LOCALES: readonly Locale[] = ["en", "es", "fr", "ru", "ja", "zh-Hans"] as const
|
const SUPPORTED_LOCALES: readonly Locale[] = ["en", "es", "fr", "ru", "ja", "zh-Hans"] as const
|
||||||
const SUPPORTED_LOCALES_BY_LOWER = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
|
||||||
|
|
||||||
const localeMessagesCache = new Map<Locale, Messages>([["en", enMessages]])
|
const messagesByLocale: Record<Locale, Messages> = {
|
||||||
const localeMessagesPromises = new Map<Locale, Promise<Messages>>()
|
en: enMessages,
|
||||||
|
es: esMessages,
|
||||||
const localeLoaders: Record<Locale, () => Promise<Messages>> = {
|
fr: frMessages,
|
||||||
en: async () => enMessages,
|
ru: ruMessages,
|
||||||
es: async () => (await import("./messages/es")).esMessages,
|
ja: jaMessages,
|
||||||
fr: async () => (await import("./messages/fr")).frMessages,
|
"zh-Hans": zhHansMessages,
|
||||||
ru: async () => (await import("./messages/ru")).ruMessages,
|
|
||||||
ja: async () => (await import("./messages/ja")).jaMessages,
|
|
||||||
"zh-Hans": async () => (await import("./messages/zh-Hans")).zhHansMessages,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeLocaleTag(value: string): string {
|
function normalizeLocaleTag(value: string): string {
|
||||||
@@ -33,7 +34,8 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
|||||||
|
|
||||||
const normalized = normalizeLocaleTag(value)
|
const normalized = normalizeLocaleTag(value)
|
||||||
const lower = normalized.toLowerCase()
|
const lower = normalized.toLowerCase()
|
||||||
const exact = SUPPORTED_LOCALES_BY_LOWER.get(lower)
|
const supportedLower = new Map(SUPPORTED_LOCALES.map((locale) => [locale.toLowerCase(), locale]))
|
||||||
|
const exact = supportedLower.get(lower)
|
||||||
if (exact) return exact
|
if (exact) return exact
|
||||||
|
|
||||||
const parts = lower.split("-")
|
const parts = lower.split("-")
|
||||||
@@ -41,11 +43,11 @@ function matchSupportedLocale(value: string | undefined): Locale | null {
|
|||||||
if (!base) return null
|
if (!base) return null
|
||||||
|
|
||||||
if (base === "zh") {
|
if (base === "zh") {
|
||||||
const zhHans = SUPPORTED_LOCALES_BY_LOWER.get("zh-hans")
|
const zhHans = supportedLower.get("zh-hans")
|
||||||
return zhHans ?? null
|
return zhHans ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMatch = SUPPORTED_LOCALES_BY_LOWER.get(base)
|
const baseMatch = supportedLower.get(base)
|
||||||
return baseMatch ?? null
|
return baseMatch ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,54 +84,8 @@ function translateFrom(messages: Messages, key: string, params?: TranslateParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [globalRevision, setGlobalRevision] = createSignal(0)
|
const [globalRevision, setGlobalRevision] = createSignal(0)
|
||||||
let globalMessages: Messages = enMessages
|
const initialGlobalLocale: Locale = detectNavigatorLocale() ?? "en"
|
||||||
let globalLocale: Locale = "en"
|
let globalMessages: Messages = messagesByLocale[initialGlobalLocale]
|
||||||
|
|
||||||
function getMessagesForLocale(locale: Locale): Messages {
|
|
||||||
return localeMessagesCache.get(locale) ?? enMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadLocaleMessages(locale: Locale): Promise<Messages> {
|
|
||||||
const cached = localeMessagesCache.get(locale)
|
|
||||||
if (cached) {
|
|
||||||
return cached
|
|
||||||
}
|
|
||||||
|
|
||||||
const pending = localeMessagesPromises.get(locale)
|
|
||||||
if (pending) {
|
|
||||||
return pending
|
|
||||||
}
|
|
||||||
|
|
||||||
const loader = localeLoaders[locale]
|
|
||||||
const promise = loader()
|
|
||||||
.then((messages) => {
|
|
||||||
localeMessagesCache.set(locale, messages)
|
|
||||||
localeMessagesPromises.delete(locale)
|
|
||||||
return messages
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
localeMessagesPromises.delete(locale)
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
|
|
||||||
localeMessagesPromises.set(locale, promise)
|
|
||||||
return promise
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function preloadLocaleMessages(preferredLocale?: string | null): Promise<Locale> {
|
|
||||||
const resolvedLocale = matchSupportedLocale(preferredLocale ?? undefined) ?? detectNavigatorLocale() ?? "en"
|
|
||||||
try {
|
|
||||||
globalMessages = await loadLocaleMessages(resolvedLocale)
|
|
||||||
globalLocale = resolvedLocale
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
return resolvedLocale
|
|
||||||
} catch {
|
|
||||||
globalMessages = enMessages
|
|
||||||
globalLocale = "en"
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
return "en"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tGlobal(key: string, params?: TranslateParams): string {
|
export function tGlobal(key: string, params?: TranslateParams): string {
|
||||||
globalRevision()
|
globalRevision()
|
||||||
@@ -145,10 +101,9 @@ const I18nContext = createContext<I18nContextValue>()
|
|||||||
|
|
||||||
export const I18nProvider: ParentComponent = (props) => {
|
export const I18nProvider: ParentComponent = (props) => {
|
||||||
const { preferences } = useConfig()
|
const { preferences } = useConfig()
|
||||||
const [detectedLocale, setDetectedLocale] = createSignal<Locale>(globalLocale)
|
const [detectedLocale, setDetectedLocale] = createSignal<Locale>("en")
|
||||||
const [resolvedLocale, setResolvedLocale] = createSignal<Locale>(globalLocale)
|
|
||||||
const previousGlobalMessages = globalMessages
|
const previousMessages = globalMessages
|
||||||
const previousGlobalLocale = globalLocale
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const detected = detectNavigatorLocale()
|
const detected = detectNavigatorLocale()
|
||||||
@@ -160,44 +115,19 @@ export const I18nProvider: ParentComponent = (props) => {
|
|||||||
return configured ?? detectedLocale() ?? "en"
|
return configured ?? detectedLocale() ?? "en"
|
||||||
})
|
})
|
||||||
|
|
||||||
const messages = createMemo<Messages>(() => getMessagesForLocale(resolvedLocale()))
|
const messages = createMemo<Messages>(() => messagesByLocale[locale()])
|
||||||
|
|
||||||
function t(key: string, params?: TranslateParams): string {
|
function t(key: string, params?: TranslateParams): string {
|
||||||
return translateFrom(messages(), key, params)
|
return translateFrom(messages(), key, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const nextLocale = locale()
|
globalMessages = messages()
|
||||||
let cancelled = false
|
setGlobalRevision((value) => value + 1)
|
||||||
|
|
||||||
void loadLocaleMessages(nextLocale)
|
|
||||||
.then((loadedMessages) => {
|
|
||||||
if (cancelled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setResolvedLocale(nextLocale)
|
|
||||||
globalLocale = nextLocale
|
|
||||||
globalMessages = loadedMessages
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (cancelled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setResolvedLocale("en")
|
|
||||||
globalMessages = enMessages
|
|
||||||
globalLocale = "en"
|
|
||||||
setGlobalRevision((value) => value + 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
cancelled = true
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
globalMessages = previousGlobalMessages
|
globalMessages = previousMessages
|
||||||
globalLocale = previousGlobalLocale
|
|
||||||
setGlobalRevision((value) => value + 1)
|
setGlobalRevision((value) => value + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ThemeProvider } from "./lib/theme"
|
|||||||
import { ConfigProvider } from "./stores/preferences"
|
import { ConfigProvider } from "./stores/preferences"
|
||||||
import { InstanceConfigProvider } from "./stores/instance-config"
|
import { InstanceConfigProvider } from "./stores/instance-config"
|
||||||
import { runtimeEnv } from "./lib/runtime-env"
|
import { runtimeEnv } from "./lib/runtime-env"
|
||||||
import { I18nProvider, preloadLocaleMessages } from "./lib/i18n"
|
import { I18nProvider } from "./lib/i18n"
|
||||||
import { storage } from "./lib/storage"
|
import { storage } from "./lib/storage"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
import "@git-diff-view/solid/styles/diff-view-pure.css"
|
||||||
@@ -31,19 +31,15 @@ async function bootstrap() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const uiConfig = await storage.loadConfigOwner("ui")
|
const uiConfig = await storage.loadConfigOwner("ui")
|
||||||
const theme = (uiConfig as any)?.theme
|
const theme = (uiConfig as any)?.theme ?? "system"
|
||||||
const locale = typeof (uiConfig as any)?.settings?.locale === "string" ? (uiConfig as any).settings.locale : undefined
|
|
||||||
|
|
||||||
if (theme === "light" || theme === "dark") {
|
if (theme === "system") {
|
||||||
document.documentElement.setAttribute("data-theme", theme)
|
|
||||||
} else {
|
|
||||||
document.documentElement.removeAttribute("data-theme")
|
document.documentElement.removeAttribute("data-theme")
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
await preloadLocaleMessages(locale)
|
|
||||||
} catch {
|
} catch {
|
||||||
// If config fails to load, fall back to CSS defaults.
|
// If config fails to load, fall back to CSS defaults.
|
||||||
await preloadLocaleMessages()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user