diff --git a/.github/workflows/get-translation-progress.yml b/.github/workflows/get-translation-progress.yml
new file mode 100644
index 00000000000..a476785c909
--- /dev/null
+++ b/.github/workflows/get-translation-progress.yml
@@ -0,0 +1,64 @@
+name: Update Crowdin translation progression
+
+on:
+ schedule:
+ - cron: "20 16 * * FRI"
+ workflow_dispatch:
+
+jobs:
+ create_pr:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Install ts-node
+ run: yarn global add ts-node
+
+ - name: Set up git
+ run: |
+ git config --global user.email "actions@github.com"
+ git config --global user.name "GitHub Action"
+
+ - name: Generate timestamp and readable date
+ id: date
+ run: |
+ echo "TIMESTAMP=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
+ echo "READABLE_DATE=$(date +'%B %-d')" >> $GITHUB_ENV
+
+ - name: Fetch latest dev and create new branch
+ run: |
+ git fetch origin dev
+ git checkout -b "automated-update-${{ env.TIMESTAMP }}" origin/dev
+
+ - name: Run script
+ run: npx ts-node -O '{"module":"commonjs"}' ./src/scripts/crowdin/getTranslationProgress.ts
+ env:
+ CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
+ CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
+
+ - name: Commit and push
+ run: |
+ git add -A
+ git commit -m "Update Crowdin translation progress"
+ git push origin "automated-update-${{ env.TIMESTAMP }}"
+
+ - name: Create PR body
+ run: |
+ echo "This PR was automatically created to update Crowdin translation progress." > pr_body.txt
+ echo "This workflows runs every Friday at 16:20 (UTC)." >> pr_body.txt
+ echo "" >> pr_body.txt
+ echo "Thank you to everyone contributing to translate ethereum.org ❤️" >> pr_body.txt
+
+ - name: Create Pull Request
+ run: |
+ gh auth login --with-token <<< ${{ secrets.GITHUB_TOKEN }}
+ gh pr create --base dev --head "automated-update-${{ env.TIMESTAMP }}" --title "Update translation progress from Crowdin - ${{ env.READABLE_DATE }}" --body-file pr_body.txt
diff --git a/i18n.config.json b/i18n.config.json
index 06b6c673ced..f6b9d006b66 100644
--- a/i18n.config.json
+++ b/i18n.config.json
@@ -297,7 +297,7 @@
},
{
"code": "ne-np",
- "crowdinCode": "ne-np",
+ "crowdinCode": "ne-NP",
"name": "Nepali",
"localName": "नेपाली",
"langDir": "ltr",
@@ -457,7 +457,7 @@
},
{
"code": "ur",
- "crowdinCode": "ur",
+ "crowdinCode": "ur-IN",
"name": "Urdu",
"localName": "اردو",
"langDir": "rtl",
diff --git a/src/components/LanguagePicker/MenuItem.tsx b/src/components/LanguagePicker/MenuItem.tsx
new file mode 100644
index 00000000000..65d660c64b0
--- /dev/null
+++ b/src/components/LanguagePicker/MenuItem.tsx
@@ -0,0 +1,127 @@
+import { useRouter } from "next/router"
+import { useTranslation } from "next-i18next"
+import { BsCheck } from "react-icons/bs"
+import {
+ Badge,
+ Box,
+ Flex,
+ forwardRef,
+ Icon,
+ MenuItem as ChakraMenuItem,
+ type MenuItemProps as ChakraMenuItemProps,
+ Text,
+} from "@chakra-ui/react"
+
+import type { LocaleDisplayInfo } from "@/lib/types"
+
+import { BaseLink } from "@/components/Link"
+
+import ProgressBar from "./ProgressBar"
+
+type ItemProps = ChakraMenuItemProps & {
+ displayInfo: LocaleDisplayInfo
+}
+
+const MenuItem = forwardRef(({ displayInfo, ...props }: ItemProps, ref) => {
+ const {
+ localeOption,
+ sourceName,
+ targetName,
+ approvalProgress,
+ wordsApproved,
+ isBrowserDefault,
+ } = displayInfo
+ const { t } = useTranslation("page-languages")
+ const { asPath, locale } = useRouter()
+ const isCurrent = localeOption === locale
+
+ const getProgressInfo = (approvalProgress: number, wordsApproved: number) => {
+ const percentage = new Intl.NumberFormat(locale!, {
+ style: "percent",
+ }).format(approvalProgress / 100)
+ const progress =
+ approvalProgress === 0 ? "<" + percentage.replace("0", "1") : percentage
+ const words = new Intl.NumberFormat(locale!).format(wordsApproved)
+ return { progress, words }
+ }
+
+ const { progress, words } = getProgressInfo(approvalProgress, wordsApproved)
+
+ return (
+ {
+ e.target.scrollIntoView({ block: "nearest" })
+ }}
+ scrollMarginY="8"
+ _hover={{ bg: "primary.lowContrast", textDecoration: "none" }}
+ _focus={{ bg: "primary.lowContrast" }}
+ sx={{
+ p: {
+ textDecoration: "none",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ },
+ }}
+ href={asPath}
+ locale={localeOption}
+ {...props}
+ >
+
+
+
+
+ {targetName}
+
+ {isBrowserDefault && (
+
+ {t("page-languages-browser-default")}
+
+ )}
+
+
+ {sourceName}
+
+
+ {isCurrent && }
+
+
+ {progress} {t("page-languages-translated")} • {words}{" "}
+ {t("page-languages-words")}
+
+
+
+ )
+})
+
+export default MenuItem
diff --git a/src/components/LanguagePicker/NoResultsCallout.tsx b/src/components/LanguagePicker/NoResultsCallout.tsx
new file mode 100644
index 00000000000..e45916839de
--- /dev/null
+++ b/src/components/LanguagePicker/NoResultsCallout.tsx
@@ -0,0 +1,33 @@
+import { useTranslation } from "next-i18next"
+import { FormHelperText, forwardRef, Text } from "@chakra-ui/react"
+
+import { BaseLink } from "@/components/Link"
+
+import MenuItem from "./MenuItem"
+
+type NoResultsCalloutProps = { onClose: () => void }
+
+const NoResultsCallout = forwardRef(
+ ({ onClose }: NoResultsCalloutProps, ref) => {
+ const { t } = useTranslation("page-languages")
+ return (
+
+
+ {t("page-languages-want-more-header")}
+
+ {t("page-languages-want-more-paragraph")}{" "}
+
+ {t("page-languages-want-more-link")}
+
+
+ )
+ }
+)
+
+export default NoResultsCallout
diff --git a/src/components/LanguagePicker/ProgressBar.tsx b/src/components/LanguagePicker/ProgressBar.tsx
new file mode 100644
index 00000000000..06bb008923f
--- /dev/null
+++ b/src/components/LanguagePicker/ProgressBar.tsx
@@ -0,0 +1,19 @@
+import { Progress, ProgressProps } from "@chakra-ui/react"
+
+type ProgressBarProps = Pick
+
+const ProgressBar = ({ value }: ProgressBarProps) => (
+
+)
+
+export default ProgressBar
diff --git a/src/components/LanguagePicker/index.tsx b/src/components/LanguagePicker/index.tsx
new file mode 100644
index 00000000000..367d9a488be
--- /dev/null
+++ b/src/components/LanguagePicker/index.tsx
@@ -0,0 +1,226 @@
+import {
+ Box,
+ Flex,
+ FormControl,
+ FormLabel,
+ Input,
+ InputGroup,
+ InputRightElement,
+ Kbd,
+ Menu,
+ MenuList,
+ type MenuListProps,
+ type MenuProps,
+ Text,
+ type UseDisclosureReturn,
+ useEventListener,
+} from "@chakra-ui/react"
+
+import { Button } from "@/components/Buttons"
+import { BaseLink } from "@/components/Link"
+
+import MenuItem from "./MenuItem"
+import NoResultsCallout from "./NoResultsCallout"
+import { useLanguagePicker } from "./useLanguagePicker"
+
+type LanguagePickerProps = Omit & {
+ children: React.ReactNode
+ placement?: MenuProps["placement"]
+ handleClose?: () => void
+ menuState?: UseDisclosureReturn
+}
+
+const LanguagePicker = ({
+ children,
+ placement,
+ handleClose,
+ menuState,
+ ...props
+}: LanguagePickerProps) => {
+ const { t, refs, disclosure, filterValue, setFilterValue, filteredNames } =
+ useLanguagePicker(handleClose, menuState)
+ const { inputRef, firstItemRef, noResultsRef, footerRef } = refs
+ const { onClose } = disclosure
+
+ /**
+ * Adds a keydown event listener to focus filter input (\).
+ * @param {string} event - The keydown event.
+ */
+ useEventListener("keydown", (e) => {
+ if (e.key !== "\\") return
+ e.preventDefault()
+ inputRef.current?.focus()
+ })
+
+ return (
+
+ )
+}
+
+export default LanguagePicker
diff --git a/src/components/LanguagePicker/useLanguagePicker.tsx b/src/components/LanguagePicker/useLanguagePicker.tsx
new file mode 100644
index 00000000000..78c3df6926d
--- /dev/null
+++ b/src/components/LanguagePicker/useLanguagePicker.tsx
@@ -0,0 +1,187 @@
+import { useEffect, useRef, useState } from "react"
+import { useRouter } from "next/router"
+import { useTranslation } from "next-i18next"
+import { useDisclosure, type UseDisclosureReturn } from "@chakra-ui/react"
+
+import type {
+ I18nLocale,
+ Lang,
+ LocaleDisplayInfo,
+ ProjectProgressData,
+} from "@/lib/types"
+
+import { MatomoEventOptions, trackCustomEvent } from "@/lib/utils/matomo"
+import { languages } from "@/lib/utils/translations"
+
+import progressData from "@/data/translationProgress.json"
+
+import { DEFAULT_LOCALE } from "@/lib/constants"
+
+const data = progressData as ProjectProgressData[]
+
+export const useLanguagePicker = (
+ handleClose?: () => void,
+ menuState?: UseDisclosureReturn
+) => {
+ const { t } = useTranslation("page-languages")
+ const { locale, locales } = useRouter()
+ const refs = {
+ inputRef: useRef(null),
+ firstItemRef: useRef(null),
+ noResultsRef: useRef(null),
+ footerRef: useRef(null),
+ }
+ const [filterValue, setFilterValue] = useState("")
+
+ const [filteredNames, setFilteredNames] = useState([])
+
+ // perform all the filtering and mapping when the filter value change
+ useEffect(() => {
+ // Get the preferred languages for the users browser
+ const navLangs = typeof navigator !== "undefined" ? navigator.languages : []
+
+ // For each browser preference, reduce to the most specific match found in `locales` array
+ const allBrowserLocales: Lang[] = navLangs
+ .map(
+ (navLang) =>
+ locales?.reduce((acc, cur) => {
+ if (cur.toLowerCase() === navLang.toLowerCase()) return cur
+ if (
+ navLang.toLowerCase().startsWith(cur.toLowerCase()) &&
+ acc !== navLang
+ )
+ return cur
+ return acc
+ }, "") as Lang
+ )
+ .filter((i) => !!i) // Remove those without matches
+
+ // Remove duplicate matches
+ const browserLocales = Array.from(new Set(allBrowserLocales))
+
+ const localeToDisplayInfo = (localeOption: Lang): LocaleDisplayInfo => {
+ const i18nItem: I18nLocale = languages[localeOption]
+ const englishName = i18nItem.name
+
+ // Get "source" display name (Language choice displayed in language of current locale)
+ const intlSource = new Intl.DisplayNames([locale!], {
+ type: "language",
+ }).of(localeOption)
+ // For languages that do not have an Intl display name, use English name as fallback
+ const fallbackSource =
+ intlSource !== localeOption ? intlSource : englishName
+ const i18nKey = "language-" + localeOption.toLowerCase()
+ const i18nSource = t(i18nKey)
+ const sourceName = i18nSource === i18nKey ? fallbackSource : i18nSource
+
+ // Get "target" display name (Language choice displayed in that language)
+ const fallbackTarget = new Intl.DisplayNames([localeOption], {
+ type: "language",
+ }).of(localeOption)
+ const i18nConfigTarget = i18nItem.localName
+ const targetName = i18nConfigTarget || fallbackTarget
+
+ if (!sourceName || !targetName) {
+ throw new Error(
+ "Missing language display name, locale: " + localeOption
+ )
+ }
+
+ // English will not have a dataItem
+ const dataItem = data.find(
+ ({ languageId }) =>
+ i18nItem.crowdinCode.toLowerCase() === languageId.toLowerCase()
+ )
+
+ const approvalProgress =
+ localeOption === DEFAULT_LOCALE ? 100 : dataItem?.approvalProgress || 0
+
+ if (data.length === 0)
+ throw new Error(
+ "Missing translation progress data; check GitHub action"
+ )
+
+ const totalWords = data[0].words.total
+
+ const wordsApproved =
+ localeOption === DEFAULT_LOCALE
+ ? totalWords || 0
+ : dataItem?.words.approved || 0
+
+ const isBrowserDefault = browserLocales.includes(localeOption)
+
+ return {
+ localeOption,
+ approvalProgress,
+ sourceName,
+ targetName,
+ englishName,
+ wordsApproved,
+ isBrowserDefault,
+ }
+ }
+
+ const displayNames: LocaleDisplayInfo[] =
+ (locales as Lang[])?.map(localeToDisplayInfo).sort((a, b) => {
+ const indexA = browserLocales.indexOf(a.localeOption as Lang)
+ const indexB = browserLocales.indexOf(b.localeOption as Lang)
+ if (indexA >= 0 && indexB >= 0) return indexA - indexB
+ if (indexA >= 0) return -1
+ if (indexB >= 0) return 1
+ return b.approvalProgress - a.approvalProgress
+ }) || []
+
+ setFilteredNames(
+ displayNames.filter(
+ ({ localeOption, sourceName, targetName, englishName }) =>
+ (localeOption + sourceName + targetName + englishName)
+ .toLowerCase()
+ .includes(filterValue.toLowerCase())
+ )
+ )
+ }, [filterValue, locale, locales, t])
+
+ const { isOpen, ...menu } = useDisclosure()
+
+ const eventBase: Pick = {
+ eventCategory: `Language picker`,
+ eventAction: "Open or close language picker",
+ }
+
+ const onOpen = () => {
+ menu.onOpen()
+ menuState?.onOpen()
+ trackCustomEvent({
+ ...eventBase,
+ eventName: "Opened",
+ } as MatomoEventOptions)
+ }
+
+ /**
+ * When closing the menu, track whether this is following a link, or simply closing the menu
+ * @param customMatomoEvent Optional custom event property overrides
+ */
+ const onClose = (
+ customMatomoEvent?: Required> &
+ Partial
+ ): void => {
+ setFilterValue("")
+ handleClose && handleClose()
+ menu.onClose()
+ menuState?.onClose()
+ trackCustomEvent(
+ (customMatomoEvent
+ ? { ...eventBase, ...customMatomoEvent }
+ : { ...eventBase, eventName: "Closed" }) satisfies MatomoEventOptions
+ )
+ }
+
+ return {
+ t,
+ refs,
+ disclosure: { isOpen, onOpen, onClose },
+ filterValue,
+ setFilterValue,
+ filteredNames,
+ }
+}
diff --git a/src/components/Nav/Mobile.tsx b/src/components/Nav/Mobile.tsx
index 2d32a8c43d3..266e473f00a 100644
--- a/src/components/Nav/Mobile.tsx
+++ b/src/components/Nav/Mobile.tsx
@@ -1,22 +1,32 @@
import React, { Fragment, ReactNode, RefObject } from "react"
import { motion } from "framer-motion"
import { useTranslation } from "next-i18next"
-import { MdBrightness2, MdLanguage, MdSearch, MdWbSunny } from "react-icons/md"
+import { IconType } from "react-icons"
+import { BsTranslate } from "react-icons/bs"
+import { MdBrightness2, MdSearch, MdWbSunny } from "react-icons/md"
import {
Box,
ButtonProps,
Drawer,
DrawerBody,
+ DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerOverlay,
Flex,
forwardRef,
+ Grid,
Icon,
+ IconButton,
List,
ListItem,
+ MenuButton,
+ Text,
+ useColorModeValue,
} from "@chakra-ui/react"
+import LanguagePicker from "@/components/LanguagePicker"
+
import type { ChildOnlyProp } from "../../lib/types"
import { Button } from "../Buttons"
import { BaseLink } from "../Link"
@@ -70,8 +80,25 @@ const FooterItem = forwardRef((props, ref) => (
/>
))
+type FooterButtonProps = ButtonProps & {
+ icon: IconType
+}
+
+const FooterButton = ({ icon, ...props }: FooterButtonProps) => (
+ }
+ sx={{ span: { m: 0 } }}
+ variant="ghost"
+ flexDir="column"
+ alignItems="center"
+ color="body.base"
+ px="1"
+ {...props}
+ />
+)
+
const FooterItemText = (props: ChildOnlyProp) => (
- void
- toggleTheme: () => void
- toggleSearch: () => void
- linkSections: ISections
- fromPageParameter: string
- drawerContainerRef: RefObject
+ onToggle: () => void
}
-const MobileNavMenu: React.FC = ({
+const HamburgerButton = ({
isMenuOpen,
- isDarkTheme,
- toggleMenu,
- toggleTheme,
- toggleSearch,
- linkSections,
- fromPageParameter,
- drawerContainerRef,
+ onToggle,
...props
-}) => {
+}: HamburgerProps) => {
const { t } = useTranslation("common")
-
- const handleClick = (): void => {
- toggleMenu()
- }
-
return (
- <>
-
+ }
+ {...props}
+ />
+ )
+}
+
+type CloseButtonProps = ButtonProps & {
+ onToggle: () => void
+}
+
+const CloseButton = ({ onToggle, ...props }: CloseButtonProps) => {
+ const { t } = useTranslation("common")
+ return (
+
+
+
+
+
+
+ }
+ {...props}
+ />
+ )
+}
+
+export interface IProps extends ButtonProps {
+ isMenuOpen: boolean
+ isDarkTheme: boolean
+ toggleMenu: () => void
+ toggleTheme: () => void
+ toggleSearch: () => void
+ linkSections: ISections
+ fromPageParameter: string
+ drawerContainerRef: RefObject
+}
+
+const MobileNavMenu: React.FC = ({
+ isMenuOpen,
+ isDarkTheme,
+ toggleMenu: onToggle,
+ toggleTheme,
+ toggleSearch,
+ linkSections,
+ fromPageParameter,
+ drawerContainerRef,
+ ...props
+}) => {
+ const { t } = useTranslation("common")
+
+ const ThemeIcon = useColorModeValue(MdBrightness2, MdWbSunny)
+ const themeLabelKey = useColorModeValue("dark-mode", "light-mode")
+
+ return (
+ <>
+
+
+ {t("close")}
+
{Object.keys(linkSections).map((sectionKey, idx) => {
@@ -204,7 +276,7 @@ const MobileNavMenu: React.FC = ({
{item.text}
{item.items.map((item, idx) => (
-
+
= ({
))}
) : (
-
+
= ({
) : (
-
+
= ({
py={0}
mt="auto"
>
- {
- // Workaround to ensure the input for the search modal can have focus
- toggleMenu()
- toggleSearch()
- }}
- >
-
- {t("search")}
-
-
-
-
- {t(isDarkTheme ? "light-mode" : "dark-mode")}
-
-
-
-
+ {
+ // Workaround to ensure the input for the search modal can have focus
+ onToggle()
+ toggleSearch()
}}
>
-
- {t("languages")}
-
-
+ {t("search")}
+
+
+ {t(themeLabelKey)}
+
+
+
+ {t("languages")}
+
+
+
diff --git a/src/components/Nav/index.tsx b/src/components/Nav/index.tsx
index 5374671b252..99f23767f03 100644
--- a/src/components/Nav/index.tsx
+++ b/src/components/Nav/index.tsx
@@ -1,13 +1,25 @@
-import React, { FC, useRef } from "react"
+import { FC, useRef } from "react"
import { useRouter } from "next/router"
import { useTranslation } from "next-i18next"
-import { MdBrightness2, MdLanguage, MdWbSunny } from "react-icons/md"
-import { Box, Flex, HStack, Icon, useDisclosure } from "@chakra-ui/react"
+import { BsTranslate } from "react-icons/bs"
+import { MdBrightness2, MdWbSunny } from "react-icons/md"
+import {
+ Box,
+ Button,
+ Flex,
+ HStack,
+ Icon,
+ MenuButton,
+ Text,
+ useDisclosure,
+ useEventListener,
+} from "@chakra-ui/react"
-import { ButtonLink, IconButton } from "../Buttons"
-import { EthHomeIcon } from "../icons"
-import { BaseLink } from "../Link"
-import Search from "../Search"
+import { IconButton } from "@/components/Buttons"
+import { EthHomeIcon } from "@/components/icons"
+import LanguagePicker from "@/components/LanguagePicker"
+import { BaseLink } from "@/components/Link"
+import Search from "@/components/Search"
import Menu from "./Menu"
import MobileNavMenu from "./Mobile"
@@ -21,7 +33,6 @@ export interface IProps {
const Nav: FC = ({ path }) => {
const {
ednLinks,
- fromPageParameter,
isDarkTheme,
shouldShowSubNav,
toggleColorMode,
@@ -32,6 +43,23 @@ const Nav: FC = ({ path }) => {
const { t } = useTranslation("common")
const searchModalDisclosure = useDisclosure()
const navWrapperRef = useRef(null)
+ const languagePickerState = useDisclosure()
+ const languagePickerRef = useRef(null)
+ /**
+ * Adds a keydown event listener to toggle color mode (ctrl|cmd + \)
+ * or open the language picker (\).
+ * @param {string} event - The keydown event.
+ */
+ useEventListener("keydown", (e) => {
+ if (e.key !== "\\") return
+ e.preventDefault()
+ if (e.metaKey || e.ctrlKey) {
+ toggleColorMode()
+ } else {
+ if (languagePickerState.isOpen) return
+ languagePickerRef.current?.click()
+ }
+ })
return (
@@ -82,6 +110,7 @@ const Nav: FC = ({ path }) => {
toggleSearch={searchModalDisclosure.onOpen}
drawerContainerRef={navWrapperRef}
/>
+ {/* Desktop */}
= ({ path }) => {
color: "primary.hover",
}}
onClick={toggleColorMode}
- >
- }
- variant="ghost"
- isSecondary
- px={1.5}
- _hover={{
- color: "primary.hover",
- "& svg": {
- transform: "rotate(10deg)",
- transition: "transform 0.5s",
- },
- }}
+ />
+
+ {/* Locale-picker menu */}
+
- {t("languages")} {locale!.toUpperCase()}
-
+
+
+
+ {t("common:languages")}
+
+ {locale!.toUpperCase()}
+
+
diff --git a/src/data/translationProgress.json b/src/data/translationProgress.json
new file mode 100644
index 00000000000..7b8f39ddf89
--- /dev/null
+++ b/src/data/translationProgress.json
@@ -0,0 +1,1549 @@
+[
+ {
+ "languageId": "af",
+ "words": {
+ "total": 336489,
+ "translated": 2654,
+ "preTranslateAppliedTo": 134,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 474,
+ "preTranslateAppliedTo": 36,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "am",
+ "words": {
+ "total": 336489,
+ "translated": 34590,
+ "preTranslateAppliedTo": 3747,
+ "approved": 9634
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3447,
+ "preTranslateAppliedTo": 362,
+ "approved": 993
+ },
+ "translationProgress": 10,
+ "approvalProgress": 2
+ },
+ {
+ "languageId": "ar",
+ "words": {
+ "total": 336489,
+ "translated": 101830,
+ "preTranslateAppliedTo": 35259,
+ "approved": 40466
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 8511,
+ "preTranslateAppliedTo": 3243,
+ "approved": 3373
+ },
+ "translationProgress": 30,
+ "approvalProgress": 12
+ },
+ {
+ "languageId": "az",
+ "words": {
+ "total": 336489,
+ "translated": 29092,
+ "preTranslateAppliedTo": 1761,
+ "approved": 18707
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2701,
+ "preTranslateAppliedTo": 310,
+ "approved": 1852
+ },
+ "translationProgress": 8,
+ "approvalProgress": 5
+ },
+ {
+ "languageId": "be",
+ "words": {
+ "total": 336489,
+ "translated": 7372,
+ "preTranslateAppliedTo": 726,
+ "approved": 5879
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 822,
+ "preTranslateAppliedTo": 94,
+ "approved": 603
+ },
+ "translationProgress": 2,
+ "approvalProgress": 1
+ },
+ {
+ "languageId": "bg",
+ "words": {
+ "total": 336489,
+ "translated": 36624,
+ "preTranslateAppliedTo": 12272,
+ "approved": 14767
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3737,
+ "preTranslateAppliedTo": 1422,
+ "approved": 1531
+ },
+ "translationProgress": 10,
+ "approvalProgress": 4
+ },
+ {
+ "languageId": "bi",
+ "words": {
+ "total": 336489,
+ "translated": 180,
+ "preTranslateAppliedTo": 20,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 115,
+ "preTranslateAppliedTo": 17,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "bn",
+ "words": {
+ "total": 336489,
+ "translated": 44527,
+ "preTranslateAppliedTo": 4114,
+ "approved": 36246
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3853,
+ "preTranslateAppliedTo": 532,
+ "approved": 3027
+ },
+ "translationProgress": 13,
+ "approvalProgress": 10
+ },
+ {
+ "languageId": "br-FR",
+ "words": {
+ "total": 336489,
+ "translated": 192,
+ "preTranslateAppliedTo": 148,
+ "approved": 82
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 29,
+ "preTranslateAppliedTo": 21,
+ "approved": 7
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "bs",
+ "words": {
+ "total": 336489,
+ "translated": 12036,
+ "preTranslateAppliedTo": 713,
+ "approved": 5879
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1160,
+ "preTranslateAppliedTo": 68,
+ "approved": 603
+ },
+ "translationProgress": 3,
+ "approvalProgress": 1
+ },
+ {
+ "languageId": "ca",
+ "words": {
+ "total": 336489,
+ "translated": 46990,
+ "preTranslateAppliedTo": 14452,
+ "approved": 19695
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 4571,
+ "preTranslateAppliedTo": 1601,
+ "approved": 2056
+ },
+ "translationProgress": 13,
+ "approvalProgress": 5
+ },
+ {
+ "languageId": "cs",
+ "words": {
+ "total": 336489,
+ "translated": 62786,
+ "preTranslateAppliedTo": 6867,
+ "approved": 26678
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 5544,
+ "preTranslateAppliedTo": 1004,
+ "approved": 2427
+ },
+ "translationProgress": 18,
+ "approvalProgress": 7
+ },
+ {
+ "languageId": "da",
+ "words": {
+ "total": 336489,
+ "translated": 15857,
+ "preTranslateAppliedTo": 2817,
+ "approved": 1466
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1668,
+ "preTranslateAppliedTo": 488,
+ "approved": 263
+ },
+ "translationProgress": 4,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "de",
+ "words": {
+ "total": 336489,
+ "translated": 237654,
+ "preTranslateAppliedTo": 43906,
+ "approved": 163737
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 18419,
+ "preTranslateAppliedTo": 4130,
+ "approved": 13313
+ },
+ "translationProgress": 70,
+ "approvalProgress": 48
+ },
+ {
+ "languageId": "el",
+ "words": {
+ "total": 336489,
+ "translated": 102635,
+ "preTranslateAppliedTo": 18079,
+ "approved": 102345
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 10333,
+ "preTranslateAppliedTo": 2113,
+ "approved": 10314
+ },
+ "translationProgress": 30,
+ "approvalProgress": 30
+ },
+ {
+ "languageId": "eo",
+ "words": {
+ "total": 336489,
+ "translated": 824,
+ "preTranslateAppliedTo": 268,
+ "approved": 169
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 77,
+ "preTranslateAppliedTo": 27,
+ "approved": 16
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "es-EM",
+ "words": {
+ "total": 336489,
+ "translated": 331819,
+ "preTranslateAppliedTo": 51273,
+ "approved": 296693
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 24558,
+ "preTranslateAppliedTo": 4649,
+ "approved": 21958
+ },
+ "translationProgress": 98,
+ "approvalProgress": 88
+ },
+ {
+ "languageId": "et",
+ "words": {
+ "total": 336489,
+ "translated": 1014,
+ "preTranslateAppliedTo": 245,
+ "approved": 75
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 149,
+ "preTranslateAppliedTo": 46,
+ "approved": 12
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "eu",
+ "words": {
+ "total": 336489,
+ "translated": 768,
+ "preTranslateAppliedTo": 217,
+ "approved": 36
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 83,
+ "preTranslateAppliedTo": 38,
+ "approved": 4
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "fa",
+ "words": {
+ "total": 336489,
+ "translated": 149845,
+ "preTranslateAppliedTo": 28269,
+ "approved": 96744
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 12201,
+ "preTranslateAppliedTo": 2782,
+ "approved": 7769
+ },
+ "translationProgress": 44,
+ "approvalProgress": 28
+ },
+ {
+ "languageId": "fa-AF",
+ "words": {
+ "total": 336489,
+ "translated": 193,
+ "preTranslateAppliedTo": 37,
+ "approved": 186
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 22,
+ "preTranslateAppliedTo": 6,
+ "approved": 17
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "fi",
+ "words": {
+ "total": 336489,
+ "translated": 45286,
+ "preTranslateAppliedTo": 11063,
+ "approved": 22594
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 4157,
+ "preTranslateAppliedTo": 1096,
+ "approved": 2136
+ },
+ "translationProgress": 13,
+ "approvalProgress": 6
+ },
+ {
+ "languageId": "fil",
+ "words": {
+ "total": 336489,
+ "translated": 63679,
+ "preTranslateAppliedTo": 5142,
+ "approved": 54718
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 5343,
+ "preTranslateAppliedTo": 656,
+ "approved": 4539
+ },
+ "translationProgress": 18,
+ "approvalProgress": 16
+ },
+ {
+ "languageId": "fr",
+ "words": {
+ "total": 336489,
+ "translated": 336489,
+ "preTranslateAppliedTo": 54154,
+ "approved": 336420
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 24685,
+ "preTranslateAppliedTo": 4849,
+ "approved": 24674
+ },
+ "translationProgress": 100,
+ "approvalProgress": 99
+ },
+ {
+ "languageId": "gi",
+ "words": {
+ "total": 336489,
+ "translated": 4,
+ "preTranslateAppliedTo": 4,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2,
+ "preTranslateAppliedTo": 2,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "gl",
+ "words": {
+ "total": 336489,
+ "translated": 8308,
+ "preTranslateAppliedTo": 1290,
+ "approved": 1062
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1042,
+ "preTranslateAppliedTo": 238,
+ "approved": 165
+ },
+ "translationProgress": 2,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "gu-IN",
+ "words": {
+ "total": 336489,
+ "translated": 3066,
+ "preTranslateAppliedTo": 1043,
+ "approved": 1300
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 551,
+ "preTranslateAppliedTo": 251,
+ "approved": 235
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "ha",
+ "words": {
+ "total": 336489,
+ "translated": 524,
+ "preTranslateAppliedTo": 114,
+ "approved": 4
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 54,
+ "preTranslateAppliedTo": 18,
+ "approved": 2
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "he",
+ "words": {
+ "total": 336489,
+ "translated": 7207,
+ "preTranslateAppliedTo": 1109,
+ "approved": 1222
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1041,
+ "preTranslateAppliedTo": 268,
+ "approved": 203
+ },
+ "translationProgress": 2,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "hi",
+ "words": {
+ "total": 336489,
+ "translated": 75996,
+ "preTranslateAppliedTo": 8937,
+ "approved": 57736
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 6278,
+ "preTranslateAppliedTo": 975,
+ "approved": 4820
+ },
+ "translationProgress": 22,
+ "approvalProgress": 17
+ },
+ {
+ "languageId": "hr",
+ "words": {
+ "total": 336489,
+ "translated": 28058,
+ "preTranslateAppliedTo": 9317,
+ "approved": 13546
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3014,
+ "preTranslateAppliedTo": 1040,
+ "approved": 1399
+ },
+ "translationProgress": 8,
+ "approvalProgress": 4
+ },
+ {
+ "languageId": "hu",
+ "words": {
+ "total": 336489,
+ "translated": 218207,
+ "preTranslateAppliedTo": 18587,
+ "approved": 148555
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 16143,
+ "preTranslateAppliedTo": 1952,
+ "approved": 11899
+ },
+ "translationProgress": 64,
+ "approvalProgress": 44
+ },
+ {
+ "languageId": "hy-AM",
+ "words": {
+ "total": 336489,
+ "translated": 10508,
+ "preTranslateAppliedTo": 1290,
+ "approved": 9634
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1094,
+ "preTranslateAppliedTo": 243,
+ "approved": 993
+ },
+ "translationProgress": 3,
+ "approvalProgress": 2
+ },
+ {
+ "languageId": "id",
+ "words": {
+ "total": 336489,
+ "translated": 280227,
+ "preTranslateAppliedTo": 37675,
+ "approved": 156274
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 21146,
+ "preTranslateAppliedTo": 3636,
+ "approved": 12116
+ },
+ "translationProgress": 83,
+ "approvalProgress": 46
+ },
+ {
+ "languageId": "ig",
+ "words": {
+ "total": 336489,
+ "translated": 31195,
+ "preTranslateAppliedTo": 1678,
+ "approved": 23475
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2954,
+ "preTranslateAppliedTo": 299,
+ "approved": 2278
+ },
+ "translationProgress": 9,
+ "approvalProgress": 6
+ },
+ {
+ "languageId": "it",
+ "words": {
+ "total": 336489,
+ "translated": 336489,
+ "preTranslateAppliedTo": 57137,
+ "approved": 336174
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 24685,
+ "preTranslateAppliedTo": 5157,
+ "approved": 24656
+ },
+ "translationProgress": 100,
+ "approvalProgress": 99
+ },
+ {
+ "languageId": "ja",
+ "words": {
+ "total": 336489,
+ "translated": 316663,
+ "preTranslateAppliedTo": 48606,
+ "approved": 284629
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 23404,
+ "preTranslateAppliedTo": 4431,
+ "approved": 20925
+ },
+ "translationProgress": 94,
+ "approvalProgress": 84
+ },
+ {
+ "languageId": "ka",
+ "words": {
+ "total": 336489,
+ "translated": 15234,
+ "preTranslateAppliedTo": 2130,
+ "approved": 1449
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1936,
+ "preTranslateAppliedTo": 387,
+ "approved": 253
+ },
+ "translationProgress": 4,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "kk",
+ "words": {
+ "total": 336489,
+ "translated": 2027,
+ "preTranslateAppliedTo": 1138,
+ "approved": 1155
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 427,
+ "preTranslateAppliedTo": 225,
+ "approved": 185
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "km",
+ "words": {
+ "total": 336489,
+ "translated": 16940,
+ "preTranslateAppliedTo": 1646,
+ "approved": 15713
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1769,
+ "preTranslateAppliedTo": 327,
+ "approved": 1551
+ },
+ "translationProgress": 5,
+ "approvalProgress": 4
+ },
+ {
+ "languageId": "kn",
+ "words": {
+ "total": 336489,
+ "translated": 44325,
+ "preTranslateAppliedTo": 1474,
+ "approved": 26051
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3595,
+ "preTranslateAppliedTo": 144,
+ "approved": 2343
+ },
+ "translationProgress": 13,
+ "approvalProgress": 7
+ },
+ {
+ "languageId": "ko",
+ "words": {
+ "total": 336489,
+ "translated": 108091,
+ "preTranslateAppliedTo": 19220,
+ "approved": 51562
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 8998,
+ "preTranslateAppliedTo": 2057,
+ "approved": 3874
+ },
+ "translationProgress": 32,
+ "approvalProgress": 15
+ },
+ {
+ "languageId": "ku",
+ "words": {
+ "total": 336489,
+ "translated": 897,
+ "preTranslateAppliedTo": 70,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 116,
+ "preTranslateAppliedTo": 40,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "ky",
+ "words": {
+ "total": 336489,
+ "translated": 456,
+ "preTranslateAppliedTo": 129,
+ "approved": 12
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 129,
+ "preTranslateAppliedTo": 55,
+ "approved": 7
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "lb",
+ "words": {
+ "total": 336489,
+ "translated": 257,
+ "preTranslateAppliedTo": 81,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 29,
+ "preTranslateAppliedTo": 11,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "lt",
+ "words": {
+ "total": 336489,
+ "translated": 4171,
+ "preTranslateAppliedTo": 1794,
+ "approved": 1567
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 790,
+ "preTranslateAppliedTo": 398,
+ "approved": 257
+ },
+ "translationProgress": 1,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "mai",
+ "words": {
+ "total": 336489,
+ "translated": 1,
+ "preTranslateAppliedTo": 1,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1,
+ "preTranslateAppliedTo": 1,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "mk",
+ "words": {
+ "total": 336489,
+ "translated": 422,
+ "preTranslateAppliedTo": 245,
+ "approved": 88
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 164,
+ "preTranslateAppliedTo": 88,
+ "approved": 16
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "ml-IN",
+ "words": {
+ "total": 336489,
+ "translated": 18507,
+ "preTranslateAppliedTo": 6285,
+ "approved": 11452
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2119,
+ "preTranslateAppliedTo": 750,
+ "approved": 1281
+ },
+ "translationProgress": 5,
+ "approvalProgress": 3
+ },
+ {
+ "languageId": "mn",
+ "words": {
+ "total": 336489,
+ "translated": 142,
+ "preTranslateAppliedTo": 131,
+ "approved": 64
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 19,
+ "preTranslateAppliedTo": 17,
+ "approved": 4
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "mr",
+ "words": {
+ "total": 336489,
+ "translated": 33873,
+ "preTranslateAppliedTo": 1592,
+ "approved": 26062
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2914,
+ "preTranslateAppliedTo": 269,
+ "approved": 2346
+ },
+ "translationProgress": 10,
+ "approvalProgress": 7
+ },
+ {
+ "languageId": "ms",
+ "words": {
+ "total": 336489,
+ "translated": 74157,
+ "preTranslateAppliedTo": 4802,
+ "approved": 37271
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 6231,
+ "preTranslateAppliedTo": 619,
+ "approved": 2879
+ },
+ "translationProgress": 22,
+ "approvalProgress": 11
+ },
+ {
+ "languageId": "my",
+ "words": {
+ "total": 336489,
+ "translated": 1568,
+ "preTranslateAppliedTo": 914,
+ "approved": 706
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 188,
+ "preTranslateAppliedTo": 99,
+ "approved": 58
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "ne-NP",
+ "words": {
+ "total": 336489,
+ "translated": 1887,
+ "preTranslateAppliedTo": 200,
+ "approved": 1434
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 317,
+ "preTranslateAppliedTo": 45,
+ "approved": 248
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "nl",
+ "words": {
+ "total": 336489,
+ "translated": 71400,
+ "preTranslateAppliedTo": 17112,
+ "approved": 37568
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 6480,
+ "preTranslateAppliedTo": 1791,
+ "approved": 3380
+ },
+ "translationProgress": 21,
+ "approvalProgress": 11
+ },
+ {
+ "languageId": "no",
+ "words": {
+ "total": 336489,
+ "translated": 6743,
+ "preTranslateAppliedTo": 1939,
+ "approved": 1717
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1319,
+ "preTranslateAppliedTo": 414,
+ "approved": 306
+ },
+ "translationProgress": 2,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "or",
+ "words": {
+ "total": 336489,
+ "translated": 146,
+ "preTranslateAppliedTo": 42,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 79,
+ "preTranslateAppliedTo": 31,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "pa-IN",
+ "words": {
+ "total": 336489,
+ "translated": 3977,
+ "preTranslateAppliedTo": 458,
+ "approved": 6
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 365,
+ "preTranslateAppliedTo": 44,
+ "approved": 2
+ },
+ "translationProgress": 1,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "pcm",
+ "words": {
+ "total": 336489,
+ "translated": 28626,
+ "preTranslateAppliedTo": 2714,
+ "approved": 17267
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2637,
+ "preTranslateAppliedTo": 350,
+ "approved": 1686
+ },
+ "translationProgress": 8,
+ "approvalProgress": 5
+ },
+ {
+ "languageId": "pl",
+ "words": {
+ "total": 336489,
+ "translated": 158045,
+ "preTranslateAppliedTo": 23871,
+ "approved": 94469
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 12909,
+ "preTranslateAppliedTo": 2437,
+ "approved": 7963
+ },
+ "translationProgress": 46,
+ "approvalProgress": 28
+ },
+ {
+ "languageId": "pt-BR",
+ "words": {
+ "total": 336489,
+ "translated": 326075,
+ "preTranslateAppliedTo": 53214,
+ "approved": 319354
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 24198,
+ "preTranslateAppliedTo": 4787,
+ "approved": 23630
+ },
+ "translationProgress": 96,
+ "approvalProgress": 94
+ },
+ {
+ "languageId": "pt-PT",
+ "words": {
+ "total": 336489,
+ "translated": 39477,
+ "preTranslateAppliedTo": 4918,
+ "approved": 26172
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3712,
+ "preTranslateAppliedTo": 775,
+ "approved": 2376
+ },
+ "translationProgress": 11,
+ "approvalProgress": 7
+ },
+ {
+ "languageId": "ro",
+ "words": {
+ "total": 336489,
+ "translated": 103193,
+ "preTranslateAppliedTo": 28227,
+ "approved": 78311
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 9188,
+ "preTranslateAppliedTo": 2632,
+ "approved": 6983
+ },
+ "translationProgress": 30,
+ "approvalProgress": 23
+ },
+ {
+ "languageId": "ru",
+ "words": {
+ "total": 336489,
+ "translated": 172802,
+ "preTranslateAppliedTo": 35985,
+ "approved": 96860
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 14092,
+ "preTranslateAppliedTo": 3599,
+ "approved": 7842
+ },
+ "translationProgress": 51,
+ "approvalProgress": 28
+ },
+ {
+ "languageId": "sat",
+ "words": {
+ "total": 336489,
+ "translated": 69,
+ "preTranslateAppliedTo": 66,
+ "approved": 57
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 26,
+ "preTranslateAppliedTo": 25,
+ "approved": 20
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "si-LK",
+ "words": {
+ "total": 336489,
+ "translated": 978,
+ "preTranslateAppliedTo": 886,
+ "approved": 706
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 134,
+ "preTranslateAppliedTo": 98,
+ "approved": 58
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "sk",
+ "words": {
+ "total": 336489,
+ "translated": 14738,
+ "preTranslateAppliedTo": 2629,
+ "approved": 6377
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1683,
+ "preTranslateAppliedTo": 439,
+ "approved": 700
+ },
+ "translationProgress": 4,
+ "approvalProgress": 1
+ },
+ {
+ "languageId": "sl",
+ "words": {
+ "total": 336489,
+ "translated": 54938,
+ "preTranslateAppliedTo": 20007,
+ "approved": 26540
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 5175,
+ "preTranslateAppliedTo": 2068,
+ "approved": 2537
+ },
+ "translationProgress": 16,
+ "approvalProgress": 7
+ },
+ {
+ "languageId": "sn",
+ "words": {
+ "total": 336489,
+ "translated": 557,
+ "preTranslateAppliedTo": 557,
+ "approved": 465
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 53,
+ "preTranslateAppliedTo": 53,
+ "approved": 40
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "so",
+ "words": {
+ "total": 336489,
+ "translated": 1238,
+ "preTranslateAppliedTo": 797,
+ "approved": 493
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 252,
+ "preTranslateAppliedTo": 156,
+ "approved": 42
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "sq",
+ "words": {
+ "total": 336489,
+ "translated": 8532,
+ "preTranslateAppliedTo": 6014,
+ "approved": 693
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1115,
+ "preTranslateAppliedTo": 741,
+ "approved": 58
+ },
+ "translationProgress": 2,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "sr-CS",
+ "words": {
+ "total": 336489,
+ "translated": 41464,
+ "preTranslateAppliedTo": 3636,
+ "approved": 26313
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3837,
+ "preTranslateAppliedTo": 504,
+ "approved": 2374
+ },
+ "translationProgress": 12,
+ "approvalProgress": 7
+ },
+ {
+ "languageId": "sv-SE",
+ "words": {
+ "total": 336489,
+ "translated": 28083,
+ "preTranslateAppliedTo": 8024,
+ "approved": 10006
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 3150,
+ "preTranslateAppliedTo": 1096,
+ "approved": 1087
+ },
+ "translationProgress": 8,
+ "approvalProgress": 2
+ },
+ {
+ "languageId": "sw",
+ "words": {
+ "total": 336489,
+ "translated": 24971,
+ "preTranslateAppliedTo": 6832,
+ "approved": 16569
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2729,
+ "preTranslateAppliedTo": 883,
+ "approved": 1784
+ },
+ "translationProgress": 7,
+ "approvalProgress": 4
+ },
+ {
+ "languageId": "ta",
+ "words": {
+ "total": 336489,
+ "translated": 8030,
+ "preTranslateAppliedTo": 1738,
+ "approved": 1453
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1041,
+ "preTranslateAppliedTo": 335,
+ "approved": 255
+ },
+ "translationProgress": 2,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "te",
+ "words": {
+ "total": 336489,
+ "translated": 13832,
+ "preTranslateAppliedTo": 1291,
+ "approved": 694
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1401,
+ "preTranslateAppliedTo": 153,
+ "approved": 59
+ },
+ "translationProgress": 4,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "tg",
+ "words": {
+ "total": 336489,
+ "translated": 169,
+ "preTranslateAppliedTo": 87,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 52,
+ "preTranslateAppliedTo": 44,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "th",
+ "words": {
+ "total": 336489,
+ "translated": 12941,
+ "preTranslateAppliedTo": 2660,
+ "approved": 5951
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 1728,
+ "preTranslateAppliedTo": 498,
+ "approved": 630
+ },
+ "translationProgress": 3,
+ "approvalProgress": 1
+ },
+ {
+ "languageId": "ti",
+ "words": {
+ "total": 336489,
+ "translated": 160,
+ "preTranslateAppliedTo": 14,
+ "approved": 0
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 17,
+ "preTranslateAppliedTo": 1,
+ "approved": 0
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "tk",
+ "words": {
+ "total": 336489,
+ "translated": 6361,
+ "preTranslateAppliedTo": 739,
+ "approved": 5881
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 709,
+ "preTranslateAppliedTo": 131,
+ "approved": 604
+ },
+ "translationProgress": 1,
+ "approvalProgress": 1
+ },
+ {
+ "languageId": "tl",
+ "words": {
+ "total": 336489,
+ "translated": 2844,
+ "preTranslateAppliedTo": 811,
+ "approved": 86
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 264,
+ "preTranslateAppliedTo": 93,
+ "approved": 8
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "tr",
+ "words": {
+ "total": 336489,
+ "translated": 326807,
+ "preTranslateAppliedTo": 44723,
+ "approved": 321705
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 24288,
+ "preTranslateAppliedTo": 4221,
+ "approved": 23859
+ },
+ "translationProgress": 97,
+ "approvalProgress": 95
+ },
+ {
+ "languageId": "uk",
+ "words": {
+ "total": 336489,
+ "translated": 191008,
+ "preTranslateAppliedTo": 34741,
+ "approved": 64755
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 15442,
+ "preTranslateAppliedTo": 3316,
+ "approved": 5426
+ },
+ "translationProgress": 56,
+ "approvalProgress": 19
+ },
+ {
+ "languageId": "ur-IN",
+ "words": {
+ "total": 336489,
+ "translated": 1998,
+ "preTranslateAppliedTo": 367,
+ "approved": 1214
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 437,
+ "preTranslateAppliedTo": 162,
+ "approved": 200
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "ur-PK",
+ "words": {
+ "total": 336489,
+ "translated": 2766,
+ "preTranslateAppliedTo": 1441,
+ "approved": 725
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 451,
+ "preTranslateAppliedTo": 191,
+ "approved": 60
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "uz",
+ "words": {
+ "total": 336489,
+ "translated": 22487,
+ "preTranslateAppliedTo": 4383,
+ "approved": 1878
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 2310,
+ "preTranslateAppliedTo": 640,
+ "approved": 339
+ },
+ "translationProgress": 6,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "vi",
+ "words": {
+ "total": 336489,
+ "translated": 62946,
+ "preTranslateAppliedTo": 12751,
+ "approved": 16174
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 5744,
+ "preTranslateAppliedTo": 1399,
+ "approved": 1635
+ },
+ "translationProgress": 18,
+ "approvalProgress": 4
+ },
+ {
+ "languageId": "yo",
+ "words": {
+ "total": 336489,
+ "translated": 3820,
+ "preTranslateAppliedTo": 930,
+ "approved": 687
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 494,
+ "preTranslateAppliedTo": 117,
+ "approved": 55
+ },
+ "translationProgress": 1,
+ "approvalProgress": 0
+ },
+ {
+ "languageId": "zh-CN",
+ "words": {
+ "total": 336489,
+ "translated": 323069,
+ "preTranslateAppliedTo": 56826,
+ "approved": 305017
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 23991,
+ "preTranslateAppliedTo": 5128,
+ "approved": 22626
+ },
+ "translationProgress": 96,
+ "approvalProgress": 90
+ },
+ {
+ "languageId": "zh-TW",
+ "words": {
+ "total": 336489,
+ "translated": 214786,
+ "preTranslateAppliedTo": 37224,
+ "approved": 111257
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 17351,
+ "preTranslateAppliedTo": 3689,
+ "approved": 8893
+ },
+ "translationProgress": 63,
+ "approvalProgress": 33
+ },
+ {
+ "languageId": "zu",
+ "words": {
+ "total": 336489,
+ "translated": 164,
+ "preTranslateAppliedTo": 164,
+ "approved": 109
+ },
+ "phrases": {
+ "total": 24685,
+ "translated": 17,
+ "preTranslateAppliedTo": 17,
+ "approved": 9
+ },
+ "translationProgress": 0,
+ "approvalProgress": 0
+ }
+]
\ No newline at end of file
diff --git a/src/intl/en/page-languages.json b/src/intl/en/page-languages.json
index 53d97878c77..9fd6feea227 100644
--- a/src/intl/en/page-languages.json
+++ b/src/intl/en/page-languages.json
@@ -11,7 +11,12 @@
"page-languages-want-more-header": "Want to see ethereum.org in a different language?",
"page-languages-want-more-link": "Translation Program",
"page-languages-want-more-paragraph": "ethereum.org translators are always translating pages in as many languages as possible. To see what they're working on right now or to sign up to join them, read about our",
- "page-languages-filter-placeholder": "Filter",
+ "page-languages-filter-label": "Filter list",
+ "page-languages-filter-placeholder": "Type to filter",
+ "page-languages-browser-default": "Browser default",
+ "page-languages-translated": "translated",
+ "page-languages-words": "words",
+ "page-languages-recruit-community": "Help us translate ethereum.org.",
"langauge-am": "Amharic",
"language-ar": "Arabic",
"language-az": "Azerbaijani",
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 94d474b89d9..18d437e134d 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -190,6 +190,99 @@ export type LocaleContributions = {
data: FileContributorData[]
}
+// Crowdin translation progress
+type Language = {
+ id: string
+ name: string
+ editorCode: string
+ twoLettersCode: string
+ threeLettersCode: string
+ locale: string
+ androidCode: string
+ osxCode: string
+ osxLocale: string
+ pluralCategoryNames: string[]
+ pluralRules: string
+ pluralExamples: string[]
+ textDirection: string
+ dialectOf: unknown
+}
+
+type CountSummary = {
+ total: number
+ translated: number
+ preTranslateAppliedTo: number
+ approved: number
+}
+
+export type ProjectProgressData = {
+ languageId: string,
+ language?: Language,
+ words: CountSummary,
+ phrases: CountSummary,
+ translationProgress: number
+ approvalProgress: number
+}
+
+export type LocaleDisplayInfo = {
+ localeOption: string
+ sourceName: string
+ targetName: string
+ englishName: string
+ approvalProgress: number
+ wordsApproved: number
+ isBrowserDefault?: boolean
+}
+
+type TranslatedStats = {
+ tmMatch: number
+ default: number
+ total: number
+}
+
+export type AllTimeData = {
+ name: string
+ url: string
+ unit: string
+ dateRange: {
+ from: string
+ to: string
+ }
+ currency: string
+ mode: string
+ totalCosts: number
+ totalTMSavings: number
+ totalPreTranslated: number
+ data: Array<{
+ user: {
+ id: number
+ username: string
+ fullName: string
+ userRole: string
+ avatarUrl: string
+ preTranslated: number
+ totalCosts: number
+ }
+ languages: Array<{
+ language: {
+ id: string
+ name: string
+ userRole: string
+ tmSavings: number
+ preTranslate: number
+ totalCosts: number
+ }
+ translated: TranslatedStats
+ targetTranslated: TranslatedStats
+ translatedByMt: TranslatedStats
+ approved: TranslatedStats
+ translationCosts: TranslatedStats
+ approvalCosts: TranslatedStats
+ }>
+ }>
+}
+
+
// GitHub contributors
export type Commit = {
commit: {
@@ -228,8 +321,8 @@ export type ToCNodeEntry = {
export type TocNodeType =
| ToCNodeEntry
| {
- items: TocNodeType[]
- }
+ items: TocNodeType[]
+ }
export type ToCItem = {
title: string
@@ -295,12 +388,12 @@ export type TimestampedData = {
export type MetricDataValue =
| {
- error: string
- }
+ error: string
+ }
| {
- data: Data
- value: Value
- }
+ data: Data
+ value: Value
+ }
export type EtherscanNodeResponse = {
result: {
@@ -358,51 +451,3 @@ export type CommunityConference = {
startDate: string
endDate: string
}
-
-type TranslatedStats = {
- tmMatch: number
- default: number
- total: number
-}
-
-export type AllTimeData = {
- name: string
- url: string
- unit: string
- dateRange: {
- from: string
- to: string
- }
- currency: string
- mode: string
- totalCosts: number
- totalTMSavings: number
- totalPreTranslated: number
- data: Array<{
- user: {
- id: number
- username: string
- fullName: string
- userRole: string
- avatarUrl: string
- preTranslated: number
- totalCosts: number
- }
- languages: Array<{
- language: {
- id: string
- name: string
- userRole: string
- tmSavings: number
- preTranslate: number
- totalCosts: number
- }
- translated: TranslatedStats
- targetTranslated: TranslatedStats
- translatedByMt: TranslatedStats
- approved: TranslatedStats
- translationCosts: TranslatedStats
- approvalCosts: TranslatedStats
- }>
- }>
-}
diff --git a/src/lib/utils/translations.ts b/src/lib/utils/translations.ts
index f9179a68c55..8c20d52edeb 100644
--- a/src/lib/utils/translations.ts
+++ b/src/lib/utils/translations.ts
@@ -31,7 +31,7 @@ export const getRequiredNamespacesForPage = (
path: string,
layout?: string | undefined
) => {
- const baseNamespaces = ["common"]
+ const baseNamespaces = ["common", "page-languages"]
const requiredNamespacesForPath = getRequiredNamespacesForPath(path)
const requiredNamespacesForLayout = getRequiredNamespacesForLayout(layout)
@@ -61,10 +61,6 @@ const getRequiredNamespacesForPath = (path: string) => {
if (path === "/contributing/translation-program/contributors") {
primaryNamespace = "page-contributing-translation-program-contributors"
- requiredNamespaces = [
- ...requiredNamespaces,
- "page-languages",
- ]
}
if (path.startsWith("/community")) {
@@ -135,10 +131,6 @@ const getRequiredNamespacesForPath = (path: string) => {
primaryNamespace = "page-get-eth"
}
- if (path.startsWith("/languages")) {
- primaryNamespace = "page-languages"
- }
-
if (path.startsWith("/roadmap/vision")) {
primaryNamespace = "page-roadmap-vision"
requiredNamespaces = [
diff --git a/src/pages/assets.tsx b/src/pages/assets.tsx
index bf05cc2fbe5..85f713fe9bb 100644
--- a/src/pages/assets.tsx
+++ b/src/pages/assets.tsx
@@ -120,7 +120,7 @@ const H3 = (props: ChildOnlyProp) => (
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("assets")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/bug-bounty.tsx b/src/pages/bug-bounty.tsx
index 3a2bd9de162..ea3b02820ce 100644
--- a/src/pages/bug-bounty.tsx
+++ b/src/pages/bug-bounty.tsx
@@ -328,7 +328,7 @@ const sortBountyHuntersFn = (a: BountyHuntersArg, b: BountyHuntersArg) => {
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("bug-bounty")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/community.tsx b/src/pages/community.tsx
index 4a3530cd39e..f79f4cb9147 100644
--- a/src/pages/community.tsx
+++ b/src/pages/community.tsx
@@ -44,7 +44,7 @@ import whatIsEthereumImg from "@/public/what-is-ethereum.png"
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/community")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/contributing/translation-program/acknowledgements.tsx b/src/pages/contributing/translation-program/acknowledgements.tsx
index 3b36816a4b0..f05b85a9452 100644
--- a/src/pages/contributing/translation-program/acknowledgements.tsx
+++ b/src/pages/contributing/translation-program/acknowledgements.tsx
@@ -53,7 +53,7 @@ export const getStaticProps = (async ({ locale }) => {
"/contributing/translation-program/acknowledgements"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
return {
props: {
diff --git a/src/pages/contributing/translation-program/contributors.tsx b/src/pages/contributing/translation-program/contributors.tsx
index c300cc2d1d0..6b3b22e8409 100644
--- a/src/pages/contributing/translation-program/contributors.tsx
+++ b/src/pages/contributing/translation-program/contributors.tsx
@@ -42,7 +42,7 @@ export const getStaticProps = (async ({ locale }) => {
"/contributing/translation-program/contributors"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
return {
props: {
diff --git a/src/pages/dapps.tsx b/src/pages/dapps.tsx
index 3f9ae06589c..05e8891e955 100644
--- a/src/pages/dapps.tsx
+++ b/src/pages/dapps.tsx
@@ -434,7 +434,7 @@ interface Categories {
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/dapps")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/developers/index.tsx b/src/pages/developers/index.tsx
index 7f9624d8846..fe021304180 100644
--- a/src/pages/developers/index.tsx
+++ b/src/pages/developers/index.tsx
@@ -139,7 +139,7 @@ const StyledCallout = chakra(Callout, {
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/developers")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/developers/learning-tools.tsx b/src/pages/developers/learning-tools.tsx
index b1fc5557892..145a44a5b6c 100644
--- a/src/pages/developers/learning-tools.tsx
+++ b/src/pages/developers/learning-tools.tsx
@@ -122,7 +122,7 @@ export const getStaticProps = (async ({ locale }) => {
"/developers/learning-tools"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/developers/local-environment.tsx b/src/pages/developers/local-environment.tsx
index 4e235a973d5..9e681a55466 100644
--- a/src/pages/developers/local-environment.tsx
+++ b/src/pages/developers/local-environment.tsx
@@ -65,7 +65,7 @@ export const getStaticProps = (async ({ locale }) => {
"/developers/local-environment"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const frameworksListData = await cachedFetchLocalEnvironmentFrameworkData()
diff --git a/src/pages/developers/tutorials.tsx b/src/pages/developers/tutorials.tsx
index fafe8ab3bc2..bba63a2f74e 100644
--- a/src/pages/developers/tutorials.tsx
+++ b/src/pages/developers/tutorials.tsx
@@ -84,7 +84,7 @@ export const getStaticProps = (async ({ locale }) => {
"/developers/tutorials"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/eth.tsx b/src/pages/eth.tsx
index e00920b1a2d..364a2523545 100644
--- a/src/pages/eth.tsx
+++ b/src/pages/eth.tsx
@@ -266,7 +266,7 @@ const CentralActionCard = (props: ComponentProps) => (
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/eth")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/gas.tsx b/src/pages/gas.tsx
index d6f7bcdc742..593f75ffbfe 100644
--- a/src/pages/gas.tsx
+++ b/src/pages/gas.tsx
@@ -100,7 +100,7 @@ const H3 = (props: HeadingProps) => (
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/gas")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/get-eth.tsx b/src/pages/get-eth.tsx
index 85bc5dd915a..4bf759cfcd6 100644
--- a/src/pages/get-eth.tsx
+++ b/src/pages/get-eth.tsx
@@ -109,7 +109,7 @@ type Props = BasePageProps & {
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("get-eth")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDataUpdateDate = getLastModifiedDateByPath(
"src/data/exchangesByCountry.ts"
diff --git a/src/pages/layer-2.tsx b/src/pages/layer-2.tsx
index 23c1b508ce9..591e3e6d555 100644
--- a/src/pages/layer-2.tsx
+++ b/src/pages/layer-2.tsx
@@ -115,7 +115,7 @@ export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/layer-2")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
return {
props: {
diff --git a/src/pages/learn.tsx b/src/pages/learn.tsx
index ce2f03c6040..740ed2d0af7 100644
--- a/src/pages/learn.tsx
+++ b/src/pages/learn.tsx
@@ -131,7 +131,7 @@ const H3 = ({ children, ...props }: HeadingProps) => (
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/learn")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/quizzes.tsx b/src/pages/quizzes.tsx
index 6c82d41b60c..0a0b5abe285 100644
--- a/src/pages/quizzes.tsx
+++ b/src/pages/quizzes.tsx
@@ -40,7 +40,7 @@ const handleGHAdd = () =>
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/quizzes")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/roadmap/vision.tsx b/src/pages/roadmap/vision.tsx
index 18e3c952228..589f9e177bb 100644
--- a/src/pages/roadmap/vision.tsx
+++ b/src/pages/roadmap/vision.tsx
@@ -119,7 +119,7 @@ const TrilemmaContent = (props: ChildOnlyProp) => (
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/roadmap/vision")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/run-a-node.tsx b/src/pages/run-a-node.tsx
index cce35ee3dc3..a496dd726cc 100644
--- a/src/pages/run-a-node.tsx
+++ b/src/pages/run-a-node.tsx
@@ -331,7 +331,7 @@ type RunANodeCard = {
export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/run-a-node")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/stablecoins.tsx b/src/pages/stablecoins.tsx
index f7e28079fbe..4da7217170d 100644
--- a/src/pages/stablecoins.tsx
+++ b/src/pages/stablecoins.tsx
@@ -94,7 +94,7 @@ export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/stablecoins")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
let marketsHasError = false
let markets: Market[] = []
diff --git a/src/pages/staking/deposit-contract.tsx b/src/pages/staking/deposit-contract.tsx
index c5a1ea19f81..6f47fa100d2 100644
--- a/src/pages/staking/deposit-contract.tsx
+++ b/src/pages/staking/deposit-contract.tsx
@@ -214,7 +214,7 @@ export const getStaticProps = (async ({ locale }) => {
"/staking/deposit-contract"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const lastDeployDate = getLastDeployDate()
diff --git a/src/pages/staking/index.tsx b/src/pages/staking/index.tsx
index 385d4e7bb29..19ef9dc2014 100644
--- a/src/pages/staking/index.tsx
+++ b/src/pages/staking/index.tsx
@@ -212,7 +212,7 @@ export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/staking")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const data = await cachedFetchBeaconchainData()
diff --git a/src/pages/wallets/find-wallet.tsx b/src/pages/wallets/find-wallet.tsx
index a2b3ae51390..9191c340482 100644
--- a/src/pages/wallets/find-wallet.tsx
+++ b/src/pages/wallets/find-wallet.tsx
@@ -95,7 +95,7 @@ export const getStaticProps = (async ({ locale }) => {
"/wallets/find-wallet"
)
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
return {
props: {
diff --git a/src/pages/wallets/index.tsx b/src/pages/wallets/index.tsx
index 30dc8f5b923..15d68c8e932 100644
--- a/src/pages/wallets/index.tsx
+++ b/src/pages/wallets/index.tsx
@@ -140,7 +140,7 @@ export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/wallets")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
return {
props: {
diff --git a/src/pages/what-is-ethereum.tsx b/src/pages/what-is-ethereum.tsx
index 96398faab72..6dab2a5b1e2 100644
--- a/src/pages/what-is-ethereum.tsx
+++ b/src/pages/what-is-ethereum.tsx
@@ -195,7 +195,7 @@ export const getStaticProps = (async ({ locale }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/what-is-ethereum")
- const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1])
+ const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[2])
const data = await cachedFetchTxCount()
diff --git a/src/scripts/crowdin/getTranslationProgress.ts b/src/scripts/crowdin/getTranslationProgress.ts
new file mode 100644
index 00000000000..bb654f6ce33
--- /dev/null
+++ b/src/scripts/crowdin/getTranslationProgress.ts
@@ -0,0 +1,30 @@
+import fs from 'fs'
+
+import type { ProjectProgressData } from "../../lib/types"
+
+import crowdin from "./api-client/crowdinClient"
+
+import "dotenv/config"
+
+async function main() {
+ const projectId = Number(process.env.CROWDIN_PROJECT_ID) || 363359
+
+ try {
+ const response = await crowdin.translationStatusApi.getProjectProgress(projectId, {
+ limit: 200,
+ })
+
+ if (!response) throw new Error("Error fetching Crowdin translation progress. Check your environment variables for a working API key.")
+
+ const progress = response.data.map(({ data }) => ({ ...data, language: undefined } satisfies ProjectProgressData))
+
+ fs.writeFileSync("src/data/translationProgress.json", JSON.stringify(progress, null, 2))
+
+ } catch (error: unknown) {
+ console.error((error as Error).message)
+ }
+}
+
+main()
+
+export default main