mirror of
https://github.com/zadam/trilium.git
synced 2026-02-27 09:03:36 +01:00
Compare commits
14 Commits
4342d77dbc
...
29cb888e22
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29cb888e22 | ||
|
|
25667e84b7 | ||
|
|
72e0d77be5 | ||
|
|
9ac4b9ed4f | ||
|
|
b3b89ba05c | ||
|
|
00dc04df25 | ||
|
|
21d47c3fef | ||
|
|
66de94f050 | ||
|
|
1917adb322 | ||
|
|
715f42b6c3 | ||
|
|
b08126ec2b | ||
|
|
e0824a5426 | ||
|
|
c4f5471e13 | ||
|
|
99215c272c |
@ -42,7 +42,7 @@
|
||||
"color": "5.0.3",
|
||||
"debounce": "3.0.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"force-graph": "1.51.1",
|
||||
"globals": "17.3.0",
|
||||
"i18next": "25.8.0",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
|
||||
@ -79,6 +79,7 @@ export const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, (() => Promise<{ de
|
||||
es: () => import("@fullcalendar/core/locales/es"),
|
||||
fr: () => import("@fullcalendar/core/locales/fr"),
|
||||
it: () => import("@fullcalendar/core/locales/it"),
|
||||
ga: null,
|
||||
cn: () => import("@fullcalendar/core/locales/zh-cn"),
|
||||
tw: () => import("@fullcalendar/core/locales/zh-tw"),
|
||||
ro: () => import("@fullcalendar/core/locales/ro"),
|
||||
|
||||
@ -34,6 +34,7 @@ const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, Options["locale"] | null>
|
||||
"en-GB": "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
ga: null,
|
||||
it: "it",
|
||||
ja: "ja",
|
||||
pt: "pt",
|
||||
|
||||
@ -2,6 +2,7 @@ import { LOCALES } from "@triliumnext/commons";
|
||||
import { readdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { LANGUAGE_MAPPINGS } from "./i18n.js";
|
||||
|
||||
const localeDir = join(__dirname, "../../../../../../node_modules/@excalidraw/excalidraw/dist/prod/locales");
|
||||
@ -21,9 +22,9 @@ describe("Canvas i18n", () => {
|
||||
for (const locale of LOCALES) {
|
||||
if (locale.contentOnly || locale.devOnly) continue;
|
||||
const languageCode = LANGUAGE_MAPPINGS[locale.id];
|
||||
if (!supportedLanguageCodes.has(languageCode)) {
|
||||
if (languageCode && !supportedLanguageCodes.has(languageCode)) {
|
||||
console.log("Supported locales:", Array.from(supportedLanguageCodes.values()).join(", "));
|
||||
expect.fail(`Unable to find locale for ${locale.id} -> ${languageCode}.`)
|
||||
expect.fail(`Unable to find locale for ${locale.id} -> ${languageCode}.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -10,6 +10,7 @@ export const LANGUAGE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, Language["code"]
|
||||
en_rtl: "en",
|
||||
es: "es-ES",
|
||||
fr: "fr-FR",
|
||||
ga: null,
|
||||
it: "it-IT",
|
||||
ja: "ja-JP",
|
||||
pt: "pt-PT",
|
||||
|
||||
@ -29,7 +29,7 @@ describe("CK config", () => {
|
||||
if (expectedLocale === "cn") expectedLocale = "zh";
|
||||
if (expectedLocale === "tw") expectedLocale = "zh-tw";
|
||||
|
||||
if (locale.id !== "en") {
|
||||
if (locale.id !== "en" && locale.id !== "ga") {
|
||||
expect((config.language as any).ui).toMatch(new RegExp(`^${expectedLocale}`));
|
||||
expect(config.translations, locale.id).toBeDefined();
|
||||
expect(config.translations, locale.id).toHaveLength(2);
|
||||
|
||||
@ -4,8 +4,9 @@ Once the Weblate translations for a single language have reached ~50% in coverag
|
||||
To do so:
|
||||
|
||||
1. In `packages/commons` look for `i18n.ts` and add a new entry to `UNSORTED_LOCALES` for the language.
|
||||
2. In `apps/server` look for `services/i18n.ts` and add a mapping for the new language in `DAYJS_LOADER`. Sort the entire list.
|
||||
2. In `packages/commons` look for `dayjs.ts` and add a mapping for the new language in `DAYJS_LOADER`. Sort the entire list.
|
||||
3. In `apps/client`, look for `collections/calendar/index.tsx` and modify `LOCALE_MAPPINGS` to add support to the new language.
|
||||
4. In `apps/client`, look for `widgets/type_widgets/canvas/i18n.ts` and modify `LANGUAGE_MAPPINGS`. A unit test ensures that the language is actually loadable.
|
||||
5. In `apps/client`, look for `widgets/type_widgets/MindMap.tsx` and modify `LOCALE_MAPPINGS`. The type definitions should already validate if the new value is supported by Mind Elixir.
|
||||
6. In `packages/ckeditor5`, look for `i18n.ts` and modify `LOCALE_MAPPINGS`. The import validation should already check if the new value is supported by CKEditor, and there's also a test to ensure it.
|
||||
6. In `packages/ckeditor5`, look for `i18n.ts` and modify `LOCALE_MAPPINGS`. The import validation should already check if the new value is supported by CKEditor, and there's also a test to ensure it.
|
||||
7. Locale mappings for PDF.js might need adjustment. To do so, in `packages/pdfjs-viewer/scripts/build.ts` there is `LOCALE_MAPPINGS`.
|
||||
@ -40,6 +40,7 @@ const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, LocaleMapping | null> = {
|
||||
coreTranslation: () => import("ckeditor5/translations/fr.js"),
|
||||
premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/fr.js"),
|
||||
},
|
||||
ga: null,
|
||||
it: {
|
||||
languageCode: "it",
|
||||
coreTranslation: () => import("ckeditor5/translations/it.js"),
|
||||
|
||||
@ -41,6 +41,7 @@ export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs
|
||||
"es": () => import("dayjs/locale/es.js"),
|
||||
"fa": () => import("dayjs/locale/fa.js"),
|
||||
"fr": () => import("dayjs/locale/fr.js"),
|
||||
"ga": () => import("dayjs/locale/ga.js"),
|
||||
"it": () => import("dayjs/locale/it.js"),
|
||||
"he": () => import("dayjs/locale/he.js"),
|
||||
"ja": () => import("dayjs/locale/ja.js"),
|
||||
|
||||
@ -19,6 +19,7 @@ const UNSORTED_LOCALES = [
|
||||
{ id: "en-GB", name: "English (United Kingdom)", electronLocale: "en_GB" },
|
||||
{ id: "es", name: "Español", electronLocale: "es" },
|
||||
{ id: "fr", name: "Français", electronLocale: "fr" },
|
||||
{ id: "ga", name: "Gaeilge", electronLocale: "en" },
|
||||
{ id: "it", name: "Italiano", electronLocale: "it" },
|
||||
{ id: "ja", name: "日本語", electronLocale: "ja" },
|
||||
{ id: "pt_br", name: "Português (Brasil)", electronLocale: "pt_BR" },
|
||||
|
||||
@ -10,7 +10,8 @@ const build = new BuildHelper("packages/pdfjs-viewer");
|
||||
const watchMode = process.argv.includes("--watch");
|
||||
|
||||
const LOCALE_MAPPINGS: Record<string, string> = {
|
||||
"es": "es-ES"
|
||||
"es": "es-ES",
|
||||
"ga": "ga-IE"
|
||||
};
|
||||
|
||||
async function main() {
|
||||
@ -28,8 +29,9 @@ async function main() {
|
||||
// Copy locales.
|
||||
const localeMappings = {};
|
||||
for (const locale of LOCALES) {
|
||||
if (locale.id === "en" || locale.contentOnly || locale.devOnly) continue;
|
||||
const mappedLocale = LOCALE_MAPPINGS[locale.electronLocale] || locale.electronLocale.replace("_", "-");
|
||||
if (locale.contentOnly || locale.devOnly) continue;
|
||||
const mappedLocale = LOCALE_MAPPINGS[locale.id] || locale.electronLocale.replace("_", "-");
|
||||
if (mappedLocale === "en") continue;
|
||||
const localePath = `${locale.id}/viewer.ftl`;
|
||||
build.copy(`viewer/locale/${mappedLocale}/viewer.ftl`, `web/locale/${localePath}`);
|
||||
localeMappings[locale.id] = localePath;
|
||||
|
||||
@ -5,6 +5,7 @@ import setupSearch from "./modules/search.js";
|
||||
import setupThemeSelector from "./modules/theme.js";
|
||||
import setupMermaid from "./modules/mermaid.js";
|
||||
import setupMath from "./modules/math.js";
|
||||
import setupSidebars from "./modules/sidebar.js";
|
||||
import api from "./modules/api.js";
|
||||
import "highlight.js/styles/default.css";
|
||||
import "@triliumnext/ckeditor5/src/theme/ck-content.css";
|
||||
@ -24,6 +25,7 @@ $try(setupToC);
|
||||
$try(setupExpanders);
|
||||
$try(setupMobileMenu);
|
||||
$try(setupSearch);
|
||||
$try(setupSidebars);
|
||||
|
||||
function setupTextNote() {
|
||||
$try(setupMermaid);
|
||||
|
||||
@ -12,21 +12,45 @@
|
||||
// }
|
||||
|
||||
export default function setupExpanders() {
|
||||
const expanders = Array.from(document.querySelectorAll("#menu .submenu-item .collapse-button"));
|
||||
const expanders = document.querySelectorAll("#menu .submenu-item .collapse-button");
|
||||
for (const expander of expanders) {
|
||||
const li = expander.parentElement?.parentElement;
|
||||
const li = expander.closest("li");
|
||||
if (!li) {
|
||||
continue;
|
||||
}
|
||||
|
||||
expander.addEventListener("click", e => {
|
||||
if ((e.target as Element).closest(".submenu-item,.item") !== li) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const ul = li.querySelector("ul")!;
|
||||
ul.style.height = `${ul.scrollHeight}px`;
|
||||
setTimeout(() => li.classList.toggle("expanded"), 1);
|
||||
setTimeout(() => ul.style.height = ``, 200);
|
||||
|
||||
const ul = li.querySelector("ul");
|
||||
if (!ul) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExpanded = li.classList.contains("expanded");
|
||||
|
||||
if (isExpanded) {
|
||||
// Collapsing
|
||||
ul.style.height = `${ul.scrollHeight}px`;
|
||||
// Force reflow
|
||||
ul.offsetHeight;
|
||||
|
||||
li.classList.remove("expanded");
|
||||
ul.style.height = "0";
|
||||
} else {
|
||||
// Expanding
|
||||
ul.style.height = "0";
|
||||
// Force reflow
|
||||
ul.offsetHeight;
|
||||
|
||||
li.classList.add("expanded");
|
||||
ul.style.height = `${ul.scrollHeight}px`;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
ul.style.height = "";
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import parents from "../common/parents.js";
|
||||
|
||||
|
||||
export default function setupMobileMenu() {
|
||||
function toggleMobileMenu(event: MouseEvent) {
|
||||
event.stopPropagation(); // Don't prevent default for links
|
||||
|
||||
const isOpen = document.body.classList.contains("menu-open");
|
||||
if (isOpen) return document.body.classList.remove("menu-open");
|
||||
return document.body.classList.add("menu-open");
|
||||
function closeMobileMenus() {
|
||||
document.body.classList.remove("menu-open");
|
||||
document.body.classList.remove("toc-open");
|
||||
}
|
||||
|
||||
const showMenuButton = document.getElementById("show-menu-button");
|
||||
showMenuButton?.addEventListener("click", toggleMobileMenu);
|
||||
|
||||
window.addEventListener("click", e => {
|
||||
const isOpen = document.body.classList.contains("menu-open");
|
||||
if (!isOpen) return; // This listener is only to close
|
||||
const isMenuOpen = document.body.classList.contains("menu-open");
|
||||
const isTocOpen = document.body.classList.contains("toc-open");
|
||||
if (!isMenuOpen && !isTocOpen) return;
|
||||
|
||||
// If the click was anywhere in the mobile nav, don't close
|
||||
if (parents(e.target as HTMLElement, "#left-pane").length) return;
|
||||
return toggleMobileMenu(e);
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// If the click was anywhere in the mobile nav or TOC, don't close
|
||||
if (target.closest("#left-pane")) return;
|
||||
if (target.closest("#toc-pane")) return;
|
||||
|
||||
// If the click was on one of the toggle buttons, the button's own listener will handle it
|
||||
if (target.closest(".header-button")) return;
|
||||
|
||||
return closeMobileMenus();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
22
packages/share-theme/src/scripts/modules/sidebar.ts
Normal file
22
packages/share-theme/src/scripts/modules/sidebar.ts
Normal file
@ -0,0 +1,22 @@
|
||||
const MOBILE_BREAKPOINT = 768; // 48em
|
||||
|
||||
function setupToggle(buttonId: string, className: string, mobileClass: string, otherMobileClass: string) {
|
||||
const button = document.getElementById(buttonId);
|
||||
if (!button) return;
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
const isMobile = window.innerWidth <= MOBILE_BREAKPOINT;
|
||||
if (isMobile) {
|
||||
document.body.classList.toggle(mobileClass);
|
||||
document.body.classList.remove(otherMobileClass);
|
||||
} else {
|
||||
const isCollapsed = document.documentElement.classList.toggle(className);
|
||||
localStorage.setItem(className, String(isCollapsed));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function setupSidebars() {
|
||||
setupToggle("left-pane-toggle-button", "left-pane-collapsed", "menu-open", "toc-open");
|
||||
setupToggle("toc-pane-toggle-button", "toc-pane-collapsed", "toc-open", "menu-open");
|
||||
}
|
||||
@ -80,3 +80,21 @@ body.type-webView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* table styles */
|
||||
.ck-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ck-content table td,
|
||||
.ck-content table th {
|
||||
min-width: 120px;
|
||||
border: 1px solid var(--background-highlight);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ck-content table th {
|
||||
background-color: var(--background-secondary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@ -2,48 +2,70 @@ html,
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#split-pane {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#left-pane {
|
||||
display: flex;
|
||||
min-width: fit-content;
|
||||
max-width: 20vw;
|
||||
height: calc(100vh);
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
width: 20vw;
|
||||
height: 100%;
|
||||
background: var(--background-secondary);
|
||||
border-right: 5px solid var(--background-highlight);
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
transition: width 0.3s ease, margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.left-pane-collapsed #left-pane {
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
margin-left: -5px; /* offset the border */
|
||||
overflow: hidden;
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
#right-pane {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#main {
|
||||
order: 2;
|
||||
max-width: 900px;
|
||||
max-width: 1600px;
|
||||
flex: 1;
|
||||
padding: 0 20px;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
transition: max-width 0.3s ease, margin 0.3s ease;
|
||||
}
|
||||
|
||||
/* #main always has max-width: 1600px and margin: 0 auto for stability */
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
#main {
|
||||
padding: 0 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,82 +1,104 @@
|
||||
#mobile-header {
|
||||
display: none;
|
||||
#header {
|
||||
background: var(--background-secondary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid var(--background-highlight);
|
||||
}
|
||||
|
||||
#mobile-header a {
|
||||
#header-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#mobile-header a img {
|
||||
#header-logo img {
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
#mobile-header button {
|
||||
.header-button {
|
||||
color: var(--text-menu);
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding: 4px;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transform: rotate(0);
|
||||
transition: background-color 200ms ease, transform 200ms ease;
|
||||
}
|
||||
|
||||
.header-button:hover {
|
||||
background-color: var(--background-highlight);
|
||||
}
|
||||
|
||||
.header-button-placeholder {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
@media (max-width: 48em) {
|
||||
|
||||
|
||||
html,
|
||||
body {
|
||||
width: unset;
|
||||
height: unset;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#split-pane {
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#right-pane, #main {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
#main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#mobile-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#mobile-header button svg {
|
||||
|
||||
#header .header-button svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#left-pane {
|
||||
|
||||
#left-pane, #toc-pane {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: auto;
|
||||
transform: translateX(-100%);
|
||||
bottom: 0;
|
||||
width: 80%;
|
||||
max-width: 300px;
|
||||
background: var(--background-secondary);
|
||||
transition: transform 200ms ease;
|
||||
z-index: 2;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.menu-open #left-pane {
|
||||
|
||||
#left-pane {
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
#toc-pane {
|
||||
display: flex;
|
||||
right: 0;
|
||||
transform: translateX(100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#toc-pane h3,
|
||||
#toc-pane #toc {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menu-open #left-pane,
|
||||
.toc-open #toc-pane {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
display: block;
|
||||
@ -90,14 +112,11 @@
|
||||
transition: background-color 200ms ease;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body.menu-open::before {
|
||||
|
||||
body.menu-open::before,
|
||||
body.toc-open::before {
|
||||
background: rgba(0,0,0, 0.6);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
body.menu-open #show-menu-button {
|
||||
background: var(--background-highlight);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,13 +4,6 @@
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#site-header > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
@ -138,4 +131,4 @@ input:checked ~ .dark-icon {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,8 @@
|
||||
}
|
||||
|
||||
#menu li:not(.expanded) > ul {
|
||||
height: 0!important;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
/* transition: height 1000ms ease; */
|
||||
}
|
||||
|
||||
@ -145,4 +146,4 @@
|
||||
|
||||
.collapse-button svg {
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,24 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
order: 3;
|
||||
/* padding: 16px 16px 16px 32px; */
|
||||
transition: width 0.3s ease, margin-right 0.3s ease;
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
padding-top: 20px;
|
||||
z-index: 1;
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.toc-pane-collapsed #toc-pane {
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toc-pane-collapsed #toc-pane h3,
|
||||
.toc-pane-collapsed #toc-pane #toc {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toc-pane h3 {
|
||||
@ -18,7 +35,7 @@
|
||||
margin: 0;
|
||||
border-radius: 6px;
|
||||
padding: 0 0 0 16px;
|
||||
max-width: 250px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#toc, #toc ul {
|
||||
@ -84,11 +101,3 @@
|
||||
#content h6 a.toc-anchor {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
#toc-pane {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -66,6 +66,13 @@
|
||||
isStatic: <%= !!isStatic %>,
|
||||
theme
|
||||
};
|
||||
|
||||
(function() {
|
||||
const leftCollapsed = localStorage.getItem("left-pane-collapsed") === "true";
|
||||
const tocCollapsed = localStorage.getItem("toc-pane-collapsed") === "true";
|
||||
if (leftCollapsed) document.documentElement.classList.add("left-pane-collapsed");
|
||||
if (tocCollapsed) document.documentElement.classList.add("toc-pane-collapsed");
|
||||
})();
|
||||
</script>
|
||||
<!-- HTML Meta Tags -->
|
||||
<meta name="description" content="<%= note.getLabelValue("shareDescription") %>">
|
||||
@ -104,21 +111,22 @@ content = content.replaceAll(headingRe, (...match) => {
|
||||
%>
|
||||
<body data-note-id="<%= note.noteId %>" class="type-<%= note.type %>" data-ancestor-note-id="<%= subRoot.note.noteId %>">
|
||||
<%- renderSnippets("body:start") %>
|
||||
<div id="mobile-header">
|
||||
<a href="<%= shareRootLink %>">
|
||||
<div id="header">
|
||||
<button aria-label="Toggle Navigation" id="left-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
|
||||
<a href="<%= shareRootLink %>" id="header-logo">
|
||||
<img src="<%= logoUrl %>" width="32" height="<%= mobileLogoHeight %>" alt="Logo" />
|
||||
<%= subRoot.note.title %>
|
||||
</a>
|
||||
<button aria-label="Show Mobile Menu" id="show-menu-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
|
||||
<% if (headingMatches.length > 1) { %>
|
||||
<button aria-label="Toggle Table of Contents" id="toc-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10V7h2v2h-2zm0 6h2v-2h-2v2z"></path></svg></button>
|
||||
<% } else { %>
|
||||
<div class="header-button-placeholder"></div>
|
||||
<% } %>
|
||||
</div>
|
||||
<div id="split-pane">
|
||||
<div id="left-pane">
|
||||
<div id="navigation">
|
||||
<div id="site-header">
|
||||
<a href="<%= shareRootLink %>">
|
||||
<img src="<%= logoUrl %>" width="<%= logoWidth %>" height="<%= logoHeight %>" alt="Logo" />
|
||||
<%= subRoot.note.title %>
|
||||
</a>
|
||||
<div class="theme-selection">
|
||||
<span id="sitetheme"><%= t("share_theme.site-theme") %></span>
|
||||
<label class="switch">
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@ -258,8 +258,8 @@ importers:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
force-graph:
|
||||
specifier: 1.51.0
|
||||
version: 1.51.0
|
||||
specifier: 1.51.1
|
||||
version: 1.51.1
|
||||
globals:
|
||||
specifier: 17.3.0
|
||||
version: 17.3.0
|
||||
@ -8799,8 +8799,8 @@ packages:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
force-graph@1.51.0:
|
||||
resolution: {integrity: sha512-aTnihCmiMA0ItLJLCbrQYS9mzriopW24goFPgUnKAAmAlPogTSmFWqoBPMXzIfPb7bs04Hur5zEI4WYgLW3Sig==}
|
||||
force-graph@1.51.1:
|
||||
resolution: {integrity: sha512-uEEX8iRzgq1IKRISOw6RrB2RLMhcI25xznQYrCTVvxZHZZ+A2jH6qIolYuwavVxAMi64pFp2yZm4KFVdD993cg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
foreach@2.0.6:
|
||||
@ -15999,6 +15999,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.4.0
|
||||
'@ckeditor/ckeditor5-utils': 47.4.0
|
||||
ckeditor5: 47.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@ -16729,6 +16731,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.4.0
|
||||
'@ckeditor/ckeditor5-utils': 47.4.0
|
||||
ckeditor5: 47.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-restricted-editing@47.4.0':
|
||||
dependencies:
|
||||
@ -25101,7 +25105,7 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
force-graph@1.51.0:
|
||||
force-graph@1.51.1:
|
||||
dependencies:
|
||||
'@tweenjs/tween.js': 25.0.0
|
||||
accessor-fn: 1.5.3
|
||||
|
||||
@ -2,20 +2,21 @@ import { LOCALES } from "../../packages/commons/src/lib/i18n";
|
||||
import { getLanguageStats } from "./utils";
|
||||
|
||||
async function main() {
|
||||
const languageStats = await getLanguageStats("client");
|
||||
const localeIdsWithCoverage = languageStats.results
|
||||
const project = "client";
|
||||
const languageStats = await getLanguageStats(project);
|
||||
const localesWithCoverage = languageStats.results
|
||||
.filter(language => language.translated_percent > 50)
|
||||
.map(language => language.language_code);
|
||||
|
||||
for (const localeId of localeIdsWithCoverage) {
|
||||
for (const localeData of localesWithCoverage) {
|
||||
const { language_code: localeId, translated_percent: percentage, language } = localeData;
|
||||
const locale = LOCALES.find(l => l.id === localeId);
|
||||
if (!locale) {
|
||||
console.error(`Locale not found for id: ${localeId}`);
|
||||
console.error(`❌ Language ${language.name} (${localeId}) has a coverage of ${percentage}% in '${project}', but it is not supported by the application.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Translation coverage check passed.");
|
||||
console.log("✅ Translation coverage check passed.");
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user