diff --git a/config/dependencies.yaml b/config/dependencies.yaml
index 839ca819aa1..c5baaa57a70 100644
--- a/config/dependencies.yaml
+++ b/config/dependencies.yaml
@@ -319,6 +319,8 @@ org:
'algoliasearch': *algoliasearch
'react-copy-to-clipboard': 5.1.0
'daisyui': *daisyui
+ 'echarts': 5.4.2
+ 'echarts-for-react': 3.0.2
'jotai': &jotai '2.2.1'
'jotai-location': &jotai-location '0.5.1'
'lodash.get': *_get
diff --git a/sites/org/components/crowdin/status.mjs b/sites/org/components/crowdin/status.mjs
new file mode 100644
index 00000000000..155fd2a8add
--- /dev/null
+++ b/sites/org/components/crowdin/status.mjs
@@ -0,0 +1,103 @@
+import { useState, useEffect } from 'react'
+import { siteConfig } from 'site/site.config.mjs'
+import { Spinner } from 'shared/components/spinner.mjs'
+import { ChartWrapper } from 'shared/components/wrappers/chart.mjs'
+import orderBy from 'lodash.orderby'
+
+/*
+ * This method load the translation status from the Crowdin API
+ */
+const loadTranslationStatus = async (setter) => {
+ let response
+ try {
+ response = await fetch(
+ `https://api.crowdin.com/api/v2/projects/${siteConfig.crowdin.projectId}/languages/progress`,
+ { headers: { Authorization: `Bearer ${siteConfig.crowdin.token}` } }
+ )
+ } catch (err) {
+ console.log(err)
+ setter(err)
+ }
+ if (response) {
+ const data = await response.json()
+ setter(data)
+ }
+}
+
+const Status = ({ done, approved, language }) => {
+ const option = {
+ series: [
+ {
+ name: 'Translation Status',
+ type: 'pie',
+ radius: ['40%', '70%'],
+ label: {
+ show: false,
+ },
+ emphasis: {
+ disabled: true,
+ },
+ data: [
+ {
+ value: approved,
+ name: 'Translated & Approved',
+ itemStyle: {
+ color: 'currentColor',
+ opacity: 1,
+ },
+ },
+ {
+ value: done,
+ name: 'Translated & Approved',
+ itemStyle: {
+ color: 'currentColor',
+ opacity: 0.7,
+ },
+ },
+ {
+ value: 100 - (done + approved),
+ name: 'Untranslated',
+ itemStyle: {
+ color: 'currentColor',
+ opacity: 0.3,
+ },
+ },
+ ],
+ },
+ ],
+ }
+
+ return
+}
+
+export const TranslationStatus = () => {
+ const [status, setStatus] = useState(false)
+
+ useEffect(() => {
+ if (!status) loadTranslationStatus(setStatus)
+ }, [status])
+
+ return status ? (
+ <>
+
+ {status.data &&
+ orderBy(status.data, ['data.approvalProgress'], ['desc']).map((entry) => (
+
+
+ {entry.data.languageId.indexOf('-') === -1
+ ? entry.data.languageId.toUpperCase()
+ : entry.data.languageId.split('-')[0].toUpperCase()}
+ : {entry.data.approvalProgress}%
+
+
+
+ ))}
+
+ >
+ ) : (
+
+ )
+}
diff --git a/sites/org/hooks/use-navigation.mjs b/sites/org/hooks/use-navigation.mjs
index ce13e82e9a5..98570460bd8 100644
--- a/sites/org/hooks/use-navigation.mjs
+++ b/sites/org/hooks/use-navigation.mjs
@@ -89,6 +89,11 @@ const sitePages = (t = false, control = 99) => {
s: 'profile',
h: 1,
},
+ translation: {
+ t: t('translation'),
+ s: 'translation',
+ h: 1,
+ },
sitemap: {
t: t('sitemap'),
s: 'sitemap',
diff --git a/sites/org/package.json b/sites/org/package.json
index 5be14a9f9ff..f6d912addca 100644
--- a/sites/org/package.json
+++ b/sites/org/package.json
@@ -39,6 +39,8 @@
"algoliasearch": "4.18.0",
"react-copy-to-clipboard": "5.1.0",
"daisyui": "3.1.7",
+ "echarts": "5.4.2",
+ "echarts-for-react": "3.0.2",
"jotai": "2.2.1",
"jotai-location": "0.5.1",
"lodash.get": "4.4.2",
diff --git a/sites/org/pages/translation/index.mjs b/sites/org/pages/translation/index.mjs
new file mode 100644
index 00000000000..da4f0f6df8a
--- /dev/null
+++ b/sites/org/pages/translation/index.mjs
@@ -0,0 +1,102 @@
+// Dependencies
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
+// Components
+import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
+import { BareLayout as Layout } from 'site/components/layouts/bare.mjs'
+import { TranslationStatus } from 'site/components/crowdin/status.mjs'
+import { Popout } from 'shared/components/popout.mjs'
+import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
+import { WebLink } from 'shared/components/web-link.mjs'
+
+// Translation namespaces used on this page
+const namespaces = [...new Set(pageNs)]
+
+// FIXME: This page (ironically) lacks translation
+//
+const TranslationPage = ({ page }) => (
+
+
+
+
Translation
+
+ Thanks to the translation volunteers in our community FreeSewing is proudly multilingual.
+
+
+
Get involved
+
+ Translation is a team effort, and getting involved is not hard at all. Refer to{' '}
+ for
+ all details.
+
+
+
Translation Status
+
+
Legend
+
+ -
+
+ Translated & Approved
+
+ -
+
+ Translated but not (yet) approved
+
+ -
+
+ Not translated (yet)
+
+
+
+
Supported Languages
+
We currently support the following five languages:
+
+ -
+ English{' '}
+
+ (This is our source language and the working language of the FreeSewing project)
+
+
+ -
+ Dutch
+
+ -
+ German
+
+ -
+ French
+
+ -
+ Spanish
+
+
+
In addition, comminity members have started initiatives to add the following langauges:
+
+
+ Looking to add a language?
+
+ We would love to make FreeSewing available in more langauges.
+
+ If you are interested in starting a new translation effort, please reach out.
+
+
+
+
+)
+
+export default TranslationPage
+
+export async function getStaticProps({ locale }) {
+ return {
+ props: {
+ ...(await serverSideTranslations(locale, namespaces)),
+ page: {
+ locale,
+ path: ['translation'],
+ },
+ },
+ }
+}
diff --git a/sites/org/site.config.mjs b/sites/org/site.config.mjs
index c22d5ea9f58..64873abee86 100644
--- a/sites/org/site.config.mjs
+++ b/sites/org/site.config.mjs
@@ -7,11 +7,16 @@ export const siteConfig = {
bugsnag: {
key: '1b3a900d6ebbfd071975e39b534e1ff5',
},
+ crowdin: {
+ projectId: 356411,
+ token: '77cf8abfabef66e1275cd4ddfe0c857d45db5e51e8349877dc50a7a740d28fb573e8a4543eca616b', // Translation status (Read-only)
+ },
sanity: {
project: process.env.SANITY_PROJECT || 'hl5bw8cj',
dataset: 'site-content',
apiVersion: '2023-06-17',
},
languages: ['en', 'es', 'de', 'fr', 'nl'],
+ languagesWip: ['uk'],
site: 'FreeSewing.org',
}
diff --git a/sites/shared/components/wrappers/chart.mjs b/sites/shared/components/wrappers/chart.mjs
new file mode 100644
index 00000000000..e45c6b3618d
--- /dev/null
+++ b/sites/shared/components/wrappers/chart.mjs
@@ -0,0 +1,19 @@
+import { useState } from 'react'
+import * as echarts from 'echarts'
+import ReactECharts from 'echarts-for-react'
+import { Popout } from 'shared/components/popout.mjs'
+
+echarts.registerTheme('light', {
+ backgroundColor: 'transparent',
+})
+echarts.registerTheme('dark', {
+ backgroundColor: 'transparent',
+})
+
+export const ChartWrapper = ({ option = false, theme = 'light', h = 400 }) => {
+ return option ? (
+
+ ) : (
+ Loading chart...
+ )
+}
diff --git a/yarn.lock b/yarn.lock
index 7c5b31134eb..0f24c7eb4d9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7894,6 +7894,22 @@ ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer "^5.0.1"
+echarts-for-react@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1"
+ integrity sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ size-sensor "^1.0.1"
+
+echarts@^5.4.2:
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.2.tgz#9f38781c9c6ae323e896956178f6956952c77a48"
+ integrity sha512-2W3vw3oI2tWJdyAz+b8DuWS0nfXtSDqlDmqgin/lfzbkB01cuMEN66KWBlmur3YMp5nEDEEt5s23pllnAzB4EA==
+ dependencies:
+ tslib "2.3.0"
+ zrender "5.4.3"
+
editorconfig@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
@@ -18126,6 +18142,11 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+size-sensor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/size-sensor/-/size-sensor-1.0.1.tgz#f84e46206d3e259faff1d548e4b3beca93219dbb"
+ integrity sha512-QTy7MnuugCFXIedXRpUSk9gUnyNiaxIdxGfUjr8xxXOqIB3QvBUYP9+b51oCg2C4dnhaeNk/h57TxjbvoJrJUA==
+
slash@3.0.0, slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -19392,6 +19413,11 @@ tsconfig-paths@^4.1.2:
minimist "^1.2.6"
strip-bom "^3.0.0"
+tslib@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
+ integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
+
tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@@ -20723,6 +20749,13 @@ zod@3.21.4:
resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==
+zrender@5.4.3:
+ version "5.4.3"
+ resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.4.3.tgz#41ffaf835f3a3210224abd9d6964b48ff01e79f5"
+ integrity sha512-DRUM4ZLnoaT0PBVvGBDO9oWIDBKFdAVieNWxWwK0niYzJCMwGchRk21/hsE+RKkIveH3XHCyvXcJDkgLVvfizQ==
+ dependencies:
+ tslib "2.3.0"
+
zwitch@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"