Skip to content

Commit

Permalink
Set up i18n package (#6991)
Browse files Browse the repository at this point in the history
* build: change package scopes

* build: create packages workspace

* feat: create initial i18n package

* feat: create import locale fn

* chore: some peaks

* refactor: remove duplicated type

* chore: set up lint

* chore: dep fix

* fix: crowdin config

* fix: fix modules, eslint

* fix: fixed lint, test and build process

* chore: code review changes

* chore: fix turbo.json

* chore: fix package versions

* fix: fix build due to shiki

* chore: (unrelated) shiki js engine

---------

Co-authored-by: Claudio Wunder <[email protected]>
  • Loading branch information
araujogui and ovflowd authored Sep 13, 2024
1 parent a2cb6d8 commit b266643
Show file tree
Hide file tree
Showing 35 changed files with 2,732 additions and 3,962 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/translations-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ on:
- 'apps/site/pages/**/*.mdx'
- '!apps/site/pages/en/**/*.md'
- '!apps/site/pages/en/**/*.mdx'
- 'apps/site/i18n/locales/*.json'
- '!apps/site/i18n/locales/en.json'
- 'packages/i18n/locales/*.json'
- '!packages/i18n/locales/en.json'

permissions:
actions: read
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ tsconfig.tsbuildinfo

# Sentry Config File
.sentryclirc

dist/
3 changes: 0 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# lint and format staged files
npx lint-staged

Expand Down
2 changes: 1 addition & 1 deletion apps/site/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import englishLocale from '@node-core/website-i18n/locales/en.json';
import { withThemeByDataAttribute } from '@storybook/addon-themes';
import type { Preview, ReactRenderer } from '@storybook/react';
import { NextIntlClientProvider } from 'next-intl';

import { STORYBOOK_MODES, STORYBOOK_SIZES } from '@/.storybook/constants';
import englishLocale from '@/i18n/locales/en.json';
import { NotificationProvider } from '@/providers/notificationProvider';

import '../next.fonts';
Expand Down
3 changes: 1 addition & 2 deletions apps/site/components/Common/LanguageDropDown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { LanguageIcon } from '@heroicons/react/24/outline';
import type { LocaleConfig } from '@node-core/website-i18n/types';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import classNames from 'classnames';
import { useTranslations } from 'next-intl';
import type { FC } from 'react';

import type { LocaleConfig } from '@/types';

import styles from './index.module.css';

type SimpleLocaleConfig = Pick<LocaleConfig, 'name' | 'code'>;
Expand Down
10 changes: 3 additions & 7 deletions apps/site/components/Downloads/Release/ReleaseCodeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import type { FC } from 'react';

import CodeBox from '@/components/Common/CodeBox';
import { ReleaseContext } from '@/providers/releaseProvider';
import { getShiki, highlightToHtml } from '@/util/getHighlighter';
import { shikiPromise, highlightToHtml } from '@/util/getHighlighter';
import { getNodeDownloadSnippet } from '@/util/getNodeDownloadSnippet';

// We cannot do top-level awaits on utilities or code that is imported by client-only components
// hence we only declare a Promise and let it be fulfilled by the first call to the function
const memoizedShiki = getShiki();
const memoizedShiki = shikiPromise.then(highlightToHtml);

const ReleaseCodeBox: FC = () => {
const { platform, os, release } = useContext(ReleaseContext);
Expand All @@ -25,9 +23,7 @@ const ReleaseCodeBox: FC = () => {
// but usually we should recommend users to download "major" versions
// since our Download Buttons get the latest minor of a major, it does make sense
// to request installation of a major via a package manager
memoizedShiki
.then(shiki => highlightToHtml(shiki)(updatedCode, 'bash'))
.then(setCode);
memoizedShiki.then(shiki => shiki(updatedCode, 'bash')).then(setCode);
// Only react when the specific release number changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [release.versionWithPrefix, os, platform]);
Expand Down
29 changes: 9 additions & 20 deletions apps/site/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { fixupPluginRules } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import js from '@eslint/js';
import importX from 'eslint-plugin-import-x';
Expand All @@ -9,27 +8,16 @@ import storybook from 'eslint-plugin-storybook';
import tseslint from 'typescript-eslint';

const compat = new FlatCompat();
const pluginToPatch = '@next/next';

const compatConfig = compat
.config({
extends: [
// https://github.com/vercel/next.js/discussions/49337
'plugin:@next/eslint-plugin-next/core-web-vitals',

// https://github.com/facebook/react/issues/28313
'plugin:react-hooks/recommended',
],
})
.map(entry => {
if (Object.hasOwn(entry.plugins, pluginToPatch)) {
entry.plugins[pluginToPatch] = fixupPluginRules(
entry.plugins[pluginToPatch]
);
}
const compatConfig = compat.config({
extends: [
// https://github.com/vercel/next.js/discussions/49337
'plugin:@next/eslint-plugin-next/core-web-vitals',

return entry;
});
// https://github.com/facebook/react/issues/28313
'plugin:react-hooks/recommended',
],
});

export default tseslint.config(
{
Expand Down Expand Up @@ -58,6 +46,7 @@ export default tseslint.config(
'no-relative-import-paths': noRelativeImportPaths,
},
rules: {
'@next/next/no-duplicate-head': 'off',
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-require-imports': 'off',
Expand Down
7 changes: 5 additions & 2 deletions apps/site/i18n.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { importLocale } from '@node-core/website-i18n';
import { getRequestConfig } from 'next-intl/server';

import { availableLocaleCodes } from '@/next.locales.mjs';
Expand All @@ -7,13 +8,15 @@ const loadLocaleDictionary = async (locale: string) => {
if (locale === 'en') {
// This enables HMR on the English Locale, so that instant refresh
// happens while we add/change texts on the source locale
return import('./i18n/locales/en.json').then(f => f.default);
return import('@node-core/website-i18n/locales/en.json').then(
f => f.default
);
}

if (availableLocaleCodes.includes(locale)) {
// Other languages don't really require HMR as they will never be development languages
// so we can load them dynamically
return import(`./i18n/locales/${locale}.json`).then(f => f.default);
return importLocale(locale);
}

throw new Error(`Unsupported locale: ${locale}`);
Expand Down
2 changes: 1 addition & 1 deletion apps/site/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
22 changes: 13 additions & 9 deletions apps/site/next.locales.mjs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
'use strict';

import localeConfig from './i18n/config.json' assert { type: 'json' };
import {
getAvailableLocales,
getAvailableLocaleCodes,
getDefaultLocale,
getAvailableLocalesMap,
getAllLocaleCodes,
} from '@node-core/website-i18n';

// As set of available and enabled locales for the website
// This is used for allowing us to redirect the user to any
// of the available locales that we have enabled on the website
const availableLocales = localeConfig.filter(locale => locale.enabled);
const availableLocales = getAvailableLocales();

// This gives an easy way of accessing all available locale codes
const availableLocaleCodes = availableLocales.map(locale => locale.code);
const availableLocaleCodes = getAvailableLocaleCodes();

// This provides the default locale information for the Next.js Application
// This is marked by the unique `locale.default` property on the `en` locale
/** @type {import('./types').LocaleConfig} */
const defaultLocale = availableLocales.find(locale => locale.default);
/** @type {import('@node-core/website-i18n/types').LocaleConfig} */
const defaultLocale = getDefaultLocale();

// Creates a Map of available locales for easy access
const availableLocalesMap = Object.fromEntries(
localeConfig.map(locale => [locale.code, locale])
);
const availableLocalesMap = getAvailableLocalesMap();

// Creates all supported locales
const allLocaleCodes = localeConfig.map(locale => locale.code);
const allLocaleCodes = getAllLocaleCodes();

export {
allLocaleCodes,
Expand Down
9 changes: 3 additions & 6 deletions apps/site/next.mdx.shiki.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import classNames from 'classnames';
import { toString } from 'hast-util-to-string';
import { SKIP, visit } from 'unist-util-visit';

import { getShiki, highlightToHast } from './util/getHighlighter';
import { shikiPromise, highlightToHast } from './util/getHighlighter';

// This is what Remark will use as prefix within a <pre> className
// to attribute the current language of the <pre> element
Expand Down Expand Up @@ -58,7 +58,7 @@ export default function rehypeShikiji() {
// We do a top-level await, since the Unist-tree visitor
// is synchronous, and it makes more sense to do a top-level
// await, rather than an await inside the visitor function
const memoizedShiki = await getShiki();
const memoizedShiki = highlightToHast(await shikiPromise);

visit(tree, 'element', (_, index, parent) => {
const languages = [];
Expand Down Expand Up @@ -174,10 +174,7 @@ export default function rehypeShikiji() {
const languageId = codeLanguage.slice(languagePrefix.length);

// Parses the <pre> contents and returns a HAST tree with the highlighted code
const { children } = highlightToHast(memoizedShiki)(
preElementContents,
languageId
);
const { children } = memoizedShiki(preElementContents, languageId);

// Adds the original language back to the <pre> element
children[0].properties.class = classNames(
Expand Down
97 changes: 47 additions & 50 deletions apps/site/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"private": true,
"name": "@nodejs/website",
"name": "@node-core/website",
"description": "Nodejs.org Website",
"homepage": "https://nodejs.org",
"repository": {
Expand Down Expand Up @@ -38,96 +38,93 @@
"dependencies": {
"@heroicons/react": "~2.1.5",
"@mdx-js/mdx": "^3.0.1",
"@node-core/website-i18n": "*",
"@nodevu/core": "~0.1.0",
"@orama/highlight": "^0.1.6",
"@oramacloud/client": "^1.3.2",
"@radix-ui/react-accessible-icon": "^1.0.3",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@oramacloud/client": "^1.3.15",
"@radix-ui/react-accessible-icon": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@savvywombat/tailwindcss-grid-areas": "~4.0.0",
"@sentry/nextjs": "~8.14.0",
"@sentry/nextjs": "~8.30.0",
"@tailwindcss/container-queries": "~0.1.1",
"@types/node": "20.16.3",
"@types/node": "20.16.5",
"@vcarl/remark-headings": "~0.1.0",
"@vercel/analytics": "~1.3.1",
"@vercel/speed-insights": "~1.0.10",
"autoprefixer": "~10.4.18",
"@vercel/speed-insights": "~1.0.12",
"autoprefixer": "~10.4.20",
"classnames": "~2.5.1",
"cross-env": "7.0.3",
"dedent": "1.5.3",
"feed": "~4.2.2",
"github-slugger": "~2.0.0",
"glob": "~10.4.1",
"glob": "~11.0.0",
"gray-matter": "~4.0.3",
"next": "~14.2.7",
"next-intl": "~3.19.0",
"next": "~14.2.11",
"next-intl": "~3.19.1",
"next-themes": "~0.3.0",
"postcss": "~8.4.40",
"postcss-calc": "~10.0.0",
"postcss": "~8.4.45",
"postcss-calc": "~10.0.2",
"postcss-import": "~16.1.0",
"postcss-mixins": "~10.0.1",
"postcss-mixins": "~11.0.1",
"postcss-simple-vars": "~7.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rehype-autolink-headings": "~7.1.0",
"rehype-slug": "~6.0.0",
"remark-gfm": "~4.0.0",
"remark-reading-time": "~2.0.1",
"semver": "~7.6.0",
"shiki": "~1.15.2",
"tailwindcss": "^3.4.7",
"typescript": "~5.5.3",
"semver": "~7.6.3",
"shiki": "~1.17.5",
"tailwindcss": "^3.4.11",
"unist-util-visit": "~5.0.0",
"vfile": "~6.0.3",
"vfile-matter": "~5.0.0"
},
"devDependencies": {
"@eslint/compat": "^1.1.1",
"@next/eslint-plugin-next": "^14.2.8",
"@storybook/addon-controls": "~8.2.7",
"@storybook/addon-interactions": "~8.2.7",
"@storybook/addon-themes": "~8.2.7",
"@storybook/addon-viewport": "~8.2.7",
"@storybook/nextjs": "~8.2.7",
"@eslint/compat": "~1.1.1",
"@next/eslint-plugin-next": "~14.2.11",
"@storybook/addon-controls": "~8.3.0",
"@storybook/addon-interactions": "~8.3.0",
"@storybook/addon-themes": "~8.3.0",
"@storybook/addon-viewport": "~8.3.0",
"@storybook/nextjs": "~8.3.0",
"@testing-library/jest-dom": "~6.5.0",
"@testing-library/react": "~16.0.1",
"@testing-library/user-event": "~14.5.2",
"@types/jest": "29.5.12",
"@types/jest": "29.5.13",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/semver": "~7.5.8",
"eslint": "^9.10.0",
"eslint-config-next": "~14.2.8",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import-x": "^4.2.1",
"eslint-plugin-mdx": "3.1.5",
"eslint-plugin-no-relative-import-paths": "^1.5.3",
"eslint-plugin-react": "^7.35.2",
"eslint-plugin-react-hooks": "5.1.0-rc.0",
"eslint-plugin-storybook": "0.9.0--canary.156.da7873a.0",
"eslint": "~9.10.0",
"eslint-config-next": "~14.2.11",
"eslint-import-resolver-typescript": "~3.6.3",
"eslint-plugin-import-x": "~4.2.1",
"eslint-plugin-mdx": "~3.1.5",
"eslint-plugin-no-relative-import-paths": "~1.5.5",
"eslint-plugin-react": "~7.36.1",
"eslint-plugin-react-hooks": "5.1.0-rc-4c58fce7-20240904",
"eslint-plugin-storybook": "0.9.0--canary.156.26b630a.0",
"handlebars": "4.7.8",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-junit": "16.0.0",
"remark-frontmatter": "5.0.0",
"remark-preset-lint-node": "5.1.2",
"storybook": "~8.2.7",
"storybook": "~8.3.0",
"stylelint": "16.9.0",
"stylelint-config-standard": "36.0.1",
"stylelint-order": "6.0.4",
"stylelint-selector-bem-pattern": "4.0.0",
"typescript-eslint": "^8.4.0",
"stylelint-selector-bem-pattern": "4.0.1",
"typescript": "~5.5.4",
"typescript-eslint": "~8.5.0",
"user-agent-data-types": "0.4.2"
},
"overrides": {
"eslint": "$eslint"
}
}
Loading

0 comments on commit b266643

Please sign in to comment.