I18n crowdin (#242)

* #241 init crowdin usage

* #241 convert all convertible copy into i18n value

---------

Co-authored-by: neil <neil@neils-MacBook-Pro.local>
This commit is contained in:
Neil 2023-02-28 13:38:57 +08:00 committed by GitHub
parent d3fa446c8c
commit 32e53ee1ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 260 additions and 93 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ yarn-error.log
.DS_Store
.pnpm-store
target
copy.json

View file

@ -88,6 +88,7 @@ pnpm dev
```sh
pnpm install
pnpm --filter desktop exec pnpm predist
pnpm --filter desktop exec pnpm dist
```

View file

@ -1,6 +1,8 @@
const { notarize } = require("@electron/notarize");
const fs = require("fs");
const path = require("path");
const otaClient = require("@crowdin/ota-client");
const _ = require("lodash");
module.exports = {
appId: "xyz.tea.gui",
@ -17,8 +19,6 @@ module.exports = {
arch: ["x64", "arm64"]
}
},
// TODO: if xcrun altool exists eventually in our self-hosted macos
// SOLUTION: is notarize separately in next pipeline step
afterSign: async (params) => {
if (process.platform !== "darwin") {
return;
@ -51,14 +51,6 @@ module.exports = {
console.log(`Done notarizing`);
},
// publish: {
// provider: "github",
// repo: "gui",
// owner: "teaxyz",
// releaseType: "release"
// },
// this determines the configuration of the auto-update feature
publish: {
provider: "generic",

View file

@ -14,6 +14,7 @@
"dev:main": "cd ./electron && vite build --config ./vite.config.js --watch",
"build:main": "cp package.json electron && cd ./electron && vite --config ./vite.config.js build --base . && rm package.json",
"pack": "electron-builder --dir --config electron-builder.config.cjs",
"predist": "node ./scripts/update-translations.cjs",
"dist": "pnpm build && electron-builder --config electron-builder.config.cjs",
"package": "pnpm build && electron-builder --config electron-builder.config.cjs",
"dev:package": "pnpm build && electron-builder --config electron-builder.config.cjs --dir",
@ -72,6 +73,7 @@
},
"type": "module",
"dependencies": {
"@crowdin/ota-client": "^0.7.0",
"@electron/asar": "^3.2.3",
"@sentry/electron": "^4.3.0",
"@sentry/svelte": "^7.38.0",

View file

@ -0,0 +1,36 @@
const fs = require("fs");
const _ = require("lodash");
const translations = require("../src/libs/translations/translations.json");
const englishRaw = translations["en"];
delete englishRaw.lang;
function flattenObject(o, prefix = "", result = {}, keepNull = true) {
if (_.isString(o) || _.isNumber(o) || _.isBoolean(o) || (keepNull && _.isNull(o))) {
result[prefix] = o;
return result;
}
if (_.isArray(o) || _.isPlainObject(o)) {
for (let i in o) {
let pref = prefix;
if (_.isArray(o)) {
pref = pref + `[${i}]`;
} else {
if (_.isEmpty(prefix)) {
pref = i;
} else {
pref = prefix + "." + i;
}
}
flattenObject(o[i], pref, result, keepNull);
}
return result;
}
return result;
}
const flattenedEnglish = flattenObject(englishRaw);
fs.writeFileSync("./copy.json", JSON.stringify(flattenedEnglish, null, 2), "utf-8");

View file

@ -0,0 +1,39 @@
const otaClient = require("@crowdin/ota-client");
const _ = require("lodash");
const fs = require("fs");
const path = require("path");
// this read only hash is from crowdin
const hash = "cf849610ca66250f0954379ct4t";
const client = new otaClient.default(hash);
async function main() {
console.log("getting latest translation!");
const [languagesList, translationsRaw] = await Promise.all([
client.getLanguageObjects(),
client.getStrings()
]);
const lang = languagesList.reduce((map, lang) => {
map[lang.id] = lang.name;
return map;
}, {});
const translations = languagesList.reduce((map, langRaw) => {
map[langRaw.id] = {
lang
};
const translation = translationsRaw[langRaw.id];
for (const k in translation) {
const key = [langRaw.id, k].join(".");
_.set(map, key, translation[k]);
}
return map;
}, {});
const translationsPath = path.join(__dirname, "../src/libs/translations/translations.json");
await fs.writeFileSync(translationsPath, JSON.stringify(translations, null, 2), "utf-8");
}
main();

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import type { AirtablePost } from '@tea/ui/types';
import Posts from '@tea/ui/posts/posts.svelte';
import PanelHeader from '@tea/ui/panel-header/panel-header.svelte';
@ -16,7 +17,7 @@
<PanelHeader {title} {ctaLabel} ctaLink="/" />
{#if courses.length}
<Posts posts={courses} linkTarget="_blank" />
<Posts posts={courses} readMoreCta={$t("post.read-more-cta")} linkTarget="_blank" />
{:else}
<section class="border-gray h-64 border bg-black p-4">
<Preloader />

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import { postsStore } from '$libs/stores';
import type { Course } from '$libs/types';
@ -20,7 +21,7 @@
</script>
<Gallery
title="FEATURED COURSES"
title={$t("documentation.featured-courses-title").toUpperCase()}
items={courses.map((course) => ({
title: course.title,
subTitle: course.sub_title,

View file

@ -1,17 +1,18 @@
<script lang="ts">
import { t } from '$libs/translations';
import Button from '@tea/ui/button/button.svelte';
import * as pub from '$env/static/public';
</script>
<footer class="font-machina relative h-auto w-full bg-black">
<section class="p-4 px-16 py-16">
<h3 class="text-primary mb-5 text-2xl">QUICK LINKS</h3>
<h3 class="text-primary mb-5 text-2xl">{$t("footer.quick-links-title").toUpperCase()}</h3>
<menu class="flex gap-4">
<div class="border-gray flex-grow border border-l-0 border-r-0">
<a href="/">
<Button>
<div class="text-primary flex justify-between hover:text-black">
<div class="uppercase">About the tea store</div>
<div class="uppercase">{$t("footer.about-tea-store").toUpperCase()}</div>
<div>&#8594</div>
</div>
</Button>
@ -21,7 +22,7 @@
<a href="/">
<Button>
<div class="text-primary flex justify-between hover:text-black">
<div class="uppercase">REPORT A PROBLEM</div>
<div class="uppercase">{$t("footer.report-a-problem").toUpperCase()}</div>
<div>&#8594</div>
</div>
</Button>
@ -31,7 +32,7 @@
<a href="https://tea.xyz" target="_blank" rel="noreferrer">
<Button>
<div class="text-primary flex justify-between hover:text-black">
<div class="uppercase">VISIT TEA.XYZ</div>
<div class="uppercase">{$t("footer.visit-website").toUpperCase()}</div>
<div>&#8594</div>
</div>
</Button>
@ -48,7 +49,7 @@
rel="noreferrer"
class="hover:text-white"
>
TERMS & SERVICES
{$t("footer.terms-services").toUpperCase()}
</a>
<a
href="https://tea.xyz/privacy-policy/"
@ -56,7 +57,7 @@
rel="noreferrer"
class="hover:text-white"
>
PRIVACY POLICY
{$t("footer.privacy-policy").toUpperCase()}
</a>
</div>
{#if pub.PUBLIC_VERSION}

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import type { GUIPackage } from '$libs/types';
import { PackageStates } from '$libs/types';
import PanelHeader from '@tea/ui/panel-header/panel-header.svelte';
@ -13,7 +14,7 @@
});
</script>
<PanelHeader title="My installs" ctaLabel="Check for updates >" ctaLink="#" />
<PanelHeader title={$t("package.my-installs-title")} ctaLabel={`${$t("package.check-for-updates")} >`} ctaLink="#" />
<ul class="border-gray grid grid-cols-3 border border-r-0 bg-black">
{#if packages.length > 0}
@ -21,7 +22,7 @@
<div class="border-gray border border-t-0 border-l-0 p-4">
<MiniPackageCard
{pkg}
ctaLabel="DETAILS"
ctaLabel={$t("package.details").toUpperCase()}
onClickCTA={async () => {
console.log('do something with:', pkg.full_name);
}}

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import { postsStore } from '$libs/stores';
import type { AirtablePost } from '@tea/ui/types';
import Posts from '@tea/ui/posts/posts.svelte';
@ -11,7 +12,7 @@
postsStore.subscribeByTag('news', (posts) => (news = posts));
</script>
<PanelHeader title="Open-source News" ctaLabel="Read more articles >" ctaLink="/" />
<PanelHeader title={$t("home.os-news-title")} ctaLabel={`${$t("post.article-more-cta")} >`} ctaLink="/" />
{#if news.length}
<Posts posts={news} linkTarget="_blank" />
{:else}

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import type { GUIPackage } from '$libs/types';
import { PackageStates } from '$libs/types';
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
@ -16,10 +17,10 @@
const getCTALabel = (state: PackageStates): string => {
return {
[PackageStates.AVAILABLE]: 'INSTALL',
[PackageStates.INSTALLED]: 'INSTALLED',
[PackageStates.INSTALLING]: 'INSTALLING',
[PackageStates.UNINSTALLED]: 'RE-INSTALL'
[PackageStates.AVAILABLE]: $t("package.install-label").toUpperCase(),
[PackageStates.INSTALLED]: $t("package.installed-label").toUpperCase(),
[PackageStates.INSTALLING]: $t("package.installing-label").toUpperCase(),
[PackageStates.UNINSTALLED]: $t("package.reinstall-label").toUpperCase(),
}[state];
};
@ -32,7 +33,7 @@
<header class="border-gray text-primary flex items-center justify-between border bg-black p-4">
<span>{title}</span>
<a href="/packages" class="font-sono text-sm underline">View all packages</a>
<a href="/packages" class="font-sono text-sm underline">{$t("package.view-all-cta")}</a>
</header>
<ul class="grid grid-cols-3 bg-black">
{#if packages.length > 0}

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import { packagesStore } from '$libs/stores';
import SortingButtons from './sorting-buttons.svelte';
import type { GUIPackage } from '$libs/types';
@ -51,10 +52,10 @@
const getCTALabel = (state: PackageStates): string => {
return {
[PackageStates.AVAILABLE]: 'INSTALL',
[PackageStates.INSTALLED]: 'INSTALLED',
[PackageStates.INSTALLING]: 'INSTALLING',
[PackageStates.UNINSTALLED]: 'RE-INSTALL'
[PackageStates.AVAILABLE]: $t("package.install-label").toUpperCase(),
[PackageStates.INSTALLED]: $t("package.installed-label").toUpperCase(),
[PackageStates.INSTALLING]: $t("package.installing-label").toUpperCase(),
[PackageStates.UNINSTALLED]: $t("package.reinstall-label").toUpperCase(),
}[state];
};
@ -64,7 +65,7 @@
<div class="border-gray border bg-black">
<section class="flex items-center justify-between">
<div>
<SearchInput size="medium" {onSearch} />
<SearchInput size="medium" {onSearch} placeholder={`${$t("search")}_`} />
</div>
<div class="pr-4">
<section class="border-gray h-12 w-48 border">

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
export let onSort: (opt: string, dir: 'asc' | 'desc') => void;
@ -8,6 +9,11 @@
const sortOptions = ['popularity', 'most recent'];
const optionLabels = {
[sortOptions[0]]: $t("sorting.popularity"),
[sortOptions[1]]: $t("sorting.most-recent")
}
const setSortBy = (opt: string) => {
sortBy = opt;
if (onSort) {
@ -22,7 +28,7 @@
<section class="sorting-container font-machina bg-black text-white">
<div class="dropdown">
<div class="dropdown-title">SORT ORDER</div>
<div class="dropdown-title">{$t("sorting.label")}</div>
<ul class="dropdown-content column flex">
{#each sortOptions as option}
<li class="flex items-center">
@ -30,7 +36,7 @@
class={`sort-btn uppercase ${sortBy === option ? 'active' : ''}`}
on:click={() => setSortBy(option)}
>
{option}
{optionLabels[option]}
</button>
<div class="direction-arrows">
<button

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { searchStore } from '$libs/stores';
import { t } from '$libs/translations';
import type { GUIPackage } from '$libs/types';
import Preloader from '@tea/ui/Preloader/Preloader.svelte';
import PackageCard from '@tea/ui/package-card/package-card.svelte';
@ -40,10 +41,10 @@
const getCTALabel = (state: PackageStates): string => {
return {
[PackageStates.AVAILABLE]: 'INSTALL',
[PackageStates.INSTALLED]: 'INSTALLED',
[PackageStates.INSTALLING]: 'INSTALLING',
[PackageStates.UNINSTALLED]: 'RE-INSTALL'
[PackageStates.AVAILABLE]: $t("package.install-label").toUpperCase(),
[PackageStates.INSTALLED]: $t("package.installed-label").toUpperCase(),
[PackageStates.INSTALLING]: $t("package.installing-label").toUpperCase(),
[PackageStates.UNINSTALLED]: $t("package.reinstall-label").toUpperCase(),
}[state];
};

View file

@ -1,5 +1,6 @@
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import type { GUIPackage } from '$libs/types';
import type { Package } from '@tea/ui/types';
import { PackageStates } from '$libs/types';
@ -15,10 +16,10 @@
const getCTALabel = (state: PackageStates): string => {
return {
[PackageStates.AVAILABLE]: 'INSTALL',
[PackageStates.INSTALLED]: 'INSTALLED',
[PackageStates.INSTALLING]: 'INSTALLING',
[PackageStates.UNINSTALLED]: 'RE-INSTALL'
[PackageStates.AVAILABLE]: $t("package.install-label").toUpperCase(),
[PackageStates.INSTALLED]: $t("package.installed-label").toUpperCase(),
[PackageStates.INSTALLING]: $t("package.installing-label").toUpperCase(),
[PackageStates.UNINSTALLED]: $t("package.reinstall-label").toUpperCase(),
}[state];
};

View file

@ -4,6 +4,7 @@
import { searchStore } from '$libs/stores';
import SearchInput from '@tea/ui/search-input/search-input.svelte';
import { navStore } from '$libs/stores';
import { t } from '$libs/translations';
import ProfileNavButton from './profile-nav-button.svelte';
import SelectLang from '$components/select-lang/select-lang.svelte';
@ -31,7 +32,7 @@
<SearchInput
class="flex-grow border border-none py-4"
size="small"
placeholder="search the tea store"
placeholder={$t("store-search-placeholder")}
{onSearch}
/>
<ul class="text-gray flex gap-4 pr-4 pt-2 align-middle">
@ -45,8 +46,8 @@
<menu
class="border-gray text-gray flex h-10 gap-4 border border-l-0 border-r-0 border-t-0 pl-4 align-middle leading-10"
>
<a href="/cli" class={currentPath === '/cli' ? 'active' : ''}>install teaCli</a>
<a href="/documentation" class={currentPath === '/documentation' ? 'active' : ''}>documentation</a
<a href="/cli" class={currentPath === '/cli' ? 'active' : ''}>{$t('cli.install')}</a>
<a href="/documentation" class={currentPath === '/documentation' ? 'active' : ''}>{$t('documentation.title')}</a
>
<a href="/packages" class={currentPath === '/packages' ? 'active' : ''}>packages</a>
<a href="https://github.com/teaxyz" target="_blank" rel="noreferrer">

View file

@ -1,6 +1,7 @@
<!-- home / discover / welcome page -->
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import ListAction from '@tea/ui/list-action/list-action.svelte';
import type { ListActionItem } from '@tea/ui/types';
import { packagesStore } from '$libs/stores';
@ -13,7 +14,7 @@
.map((pkg) => ({
title: pkg.full_name,
sub_title: pkg.version,
action_label: 'INSTALL',
action_label: $t("package.install-label").toUpperCase(),
image_url: pkg.thumb_image_url,
detail_url: `/packages/${pkg.slug}`
}));
@ -25,8 +26,8 @@
</script>
<ListAction
title="Top packages this week"
mainCtaTitle="VIEW ALL SCRIPTS"
title={$t("package.top-list-title")}
mainCtaTitle={$t("package.view-all-cta").toUpperCase()}
items={items}
onSelectItem={onSelectPackage}
/>

View file

@ -1,47 +1,49 @@
<!-- home / discover / welcome page -->
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import type { ListActionItem } from '@tea/ui/types';
import ListAction from '@tea/ui/list-action/list-action.svelte';
const action_label = $t("script.use-label");
const items: ListActionItem[] = [
{
title: 'Script A',
sub_title: 'v 0.0.1',
image_url: '/images/bored-ape.png',
action_label: 'USE'
action_label
},
{
title: 'Script B',
sub_title: 'v 0.1.1',
image_url: '/images/bored-ape.png',
action_label: 'USE'
action_label
},
{
title: 'Script C',
sub_title: 'v 2.0.1',
image_url: '/images/bored-ape.png',
action_label: 'USE'
action_label
},
{
title: 'Script D',
sub_title: 'v 0.1.1',
image_url: '/images/bored-ape.png',
action_label: 'USE'
action_label
},
{
title: 'Script E',
sub_title: 'v 0.1.1',
image_url: '/images/bored-ape.png',
action_label: 'USE'
action_label
},
];
</script>
<ListAction
title="Top scripts this week"
mainCtaTitle="VIEW ALL SCRIPTS"
title={$t("script.top-list-title")}
mainCtaTitle={$t("script.view-all-cta").toUpperCase()}
items={items}
onSelectItem={async (script) => {
console.log(script)

View file

@ -1,9 +1,10 @@
import i18n from "sveltekit-i18n";
import translations from "./translations";
import translations from "./translations.json";
/** @type {import('sveltekit-i18n').Config} */
const config = {
initLocale: "en",
fallbackLocale: "en",
translations
};

View file

@ -1,4 +0,0 @@
{
"en": "English",
"cs": "Česky"
}

View file

@ -0,0 +1,65 @@
{
"en": {
"lang": {
"en": "English"
},
"cli": {
"install": "install tea"
},
"store-search-placeholder": "search the tea store",
"search": "search",
"home": {
"discover-title": "DISCOVER",
"asset-title": "ASSET TYPE",
"tutorials-title": "TUTORIALS",
"os-news-title": "OPEN-SOURCE NEWS"
},
"package": {
"top-list-title": "Top packages this week",
"browse-cta": "Browse packages",
"what-title": "What are packages?",
"short-description": "Collections of files aggregated to form larger frameworks & functions. Think Python or Node.js.",
"foundation-essentials-title": "FOUNDATION ESSENTIALS",
"view-all-cta": "View all packages",
"install-label": "install",
"installed-label": "installed",
"installing-label": "installing",
"reinstall-label": "re-install",
"my-installs-title": "my installs",
"check-for-updates": "check for updates",
"details": "details"
},
"script": {
"top-list-title": "Top scripts this week",
"view-all-cta": "View all scripts",
"use-label": "use",
"browse-cta": "Browse scripts",
"what-title": "What are scripts?",
"short-description": "Invisible applications that chain packages together in order to perform cool actions on your computer."
},
"post": {
"workshops-title": "workshops to get started",
"article-more-cta": "Read more articles",
"read-more-cta": "read more"
},
"footer": {
"quick-links-title": "quick links",
"about-tea-store": "about the tea store",
"report-a-problem": "report a problem",
"visit-website": "visit tea.xyz",
"terms-services": "terms & services",
"privacy-policy": "privacy-policy"
},
"documentation": {
"title": "documentation",
"featured-courses-title": "featured courses",
"workshops": "workshops"
},
"view-all": "view all",
"sorting": {
"label": "sort order",
"popularity": "popularity",
"most-recent": "most recent"
}
}
}

View file

@ -1,16 +0,0 @@
import lang from "./lang.json";
export default {
en: {
lang,
cli: {
install: "install tea"
}
},
cs: {
lang,
cli: {
install: "nainstalovat cli"
}
}
};

View file

@ -1,6 +1,7 @@
<!-- home / discover / welcome page -->
<script lang="ts">
import '$appcss';
import { t } from '$libs/translations';
import PageHeader from '$components/page-header/page-header.svelte';
import EssentialWorkshops from '$components/essential-workshops/essential-workshops.svelte';
import Packages from '$components/packages/packages.svelte';
@ -12,40 +13,40 @@
</script>
<div>
<PageHeader coverUrl="/images/headers/header_bg_1.png">Discover</PageHeader>
<PageHeader coverUrl="/images/headers/header_bg_1.png">{$t("home.discover-title")}</PageHeader>
<section class="mt-8 mb-8">
<Packages title="FOUNDATION ESSENTIALS" category="foundation_essentials" />
<Packages title={$t("package.foundation-essentials-title")} category="foundation_essentials" />
</section>
<PageHeader coverUrl="/images/headers/header_bg_1.png">ASSET TYPE</PageHeader>
<PageHeader coverUrl="/images/headers/header_bg_1.png">{$t("home.asset-title")}</PageHeader>
<section class="mt-8 mb-8 flex gap-4">
<div>
<HeaderCard
title="Browse Packages"
title={$t("package.browse-cta")}
imgUrl="/images/bored-ape.png"
ctaUrl="/packages"
ctaLabel="Browse packages >"
articleTitle="What are packages?"
description="Collections of files aggregated to form larger frameworks & functions. Think Python or Node.js."
ctaLabel={`${$t("package.browse-cta")} >`}
articleTitle={$t("package.what-title")}
description={$t("package.short-description")}
/>
<TopPackages />
</div>
<div>
<HeaderCard
title="Browse Scripts"
title={$t("script.browse-cta")}
imgUrl="/images/bored-ape.png"
ctaUrl="/packages"
ctaLabel="Browse scripts >"
articleTitle="What are scripts?"
description="Invisible applications that chain packages together in order to perform cool actions on your computer."
ctaLabel={`${$t("script.browse-cta")} >`}
articleTitle={$t("script.what-title")}
description={$t("script.short-description")}
/>
<TopScripts/>
</div>
</section>
<PageHeader coverUrl="/images/headers/header_bg_1.png">TUTORIALS</PageHeader>
<PageHeader coverUrl="/images/headers/header_bg_1.png">{$t("home.tutorials-title")}</PageHeader>
<section class="mt-8 mb-8">
<EssentialWorkshops title="WORKSHOPS TO GET STARTED" ctaLabel="Read more articles >" />
<EssentialWorkshops title={$t("post.workshops-title")} ctaLabel={`${$t('post.article-more-cta')} >`} />
</section>
<PageHeader coverUrl="/images/headers/header_bg_1.png">OPEN-SOURCE NEWS</PageHeader>
<PageHeader coverUrl="/images/headers/header_bg_1.png">{$t("home.os-news-title")}</PageHeader>
<section class="mt-8">
<News />
</section>

View file

@ -1,18 +1,22 @@
<script>
import '$appcss';
import { t } from '$libs/translations';
import PageHeader from '$components/page-header/page-header.svelte';
import FeaturedCourses from '$components/featured-courses/featured-courses.svelte';
import EssentialWorkshops from '$components/essential-workshops/essential-workshops.svelte';
</script>
<div>
<PageHeader>Documentation</PageHeader>
<PageHeader>{$t('documentation.title').toUpperCase()}</PageHeader>
<section>
<FeaturedCourses />
</section>
<section class="mt-8">
<EssentialWorkshops />
<EssentialWorkshops
title={$t('documentation.workshops').toUpperCase()}
ctaLabel={$t("view-all")}
/>
</section>
</div>

View file

@ -3,6 +3,7 @@
import type { AirtablePost } from "../types";
export let linkTarget = "";
export let readMoreCta = "read more";
export let posts: AirtablePost[] = [];
</script>
@ -17,7 +18,7 @@
<h1 class="text-xl text-primary">{article.title}</h1>
<p class="my-4 text-sm line-clamp-4">{article.short_description}</p>
<a href={article.link} target={linkTarget} class="text-sm text-primary underline"
>Read more ...</a
>{readMoreCta} ...</a
>
</section>
</article>

View file

@ -19,6 +19,8 @@
"url": "https://github.com/teaxyz/gui.git"
},
"dependencies": {
"@crowdin/ota-client": "^0.7.0",
"lodash": "^4.17.21",
"yaml": "^2.2.1"
}
}

View file

@ -4,12 +4,17 @@ importers:
.:
specifiers:
'@crowdin/ota-client': ^0.7.0
lodash: ^4.17.21
yaml: ^2.2.1
dependencies:
'@crowdin/ota-client': 0.7.0
lodash: 4.17.21
yaml: 2.2.1
modules/desktop:
specifiers:
'@crowdin/ota-client': ^0.7.0
'@electron/asar': ^3.2.3
'@electron/notarize': ^1.2.3
'@playwright/experimental-ct-svelte': ^1.29.2
@ -77,6 +82,7 @@ importers:
vitest: ^0.28.3
yaml: ^2.2.1
dependencies:
'@crowdin/ota-client': 0.7.0
'@electron/asar': 3.2.3
'@sentry/electron': 4.3.0
'@sentry/svelte': 7.38.0_svelte@3.55.1
@ -1682,6 +1688,15 @@ packages:
dev: true
optional: true
/@crowdin/ota-client/0.7.0:
resolution: {integrity: sha512-RNFnR2ter5dsFJlyQysT8jswDn/TBF/ALosiDRpaj2tcOlAk/amQOoqEMJrCdR46xWqMUNwz/hJ4YIlcN51kPQ==}
engines: {node: '>=12.9.0'}
dependencies:
axios: 0.25.0
transitivePeerDependencies:
- debug
dev: false
/@design-systems/utils/2.12.0:
resolution: {integrity: sha512-Y/d2Zzr+JJfN6u1gbuBUb1ufBuLMJJRZQk+dRmw8GaTpqKx5uf7cGUYGTwN02dIb3I+Tf+cW8jcGBTRiFxdYFg==}
peerDependencies:
@ -5098,6 +5113,14 @@ packages:
engines: {node: '>= 0.4'}
dev: true
/axios/0.25.0:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies:
follow-redirects: 1.15.2
transitivePeerDependencies:
- debug
dev: false
/axios/1.3.2:
resolution: {integrity: sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==}
dependencies: