Wordings can now use ICU format
This commit is contained in:
parent
77e25c9056
commit
be1ea95b71
|
@ -1,4 +1,5 @@
|
|||
src/graphql/generated.ts
|
||||
src/graphql/icuParams.ts
|
||||
src/shared
|
||||
.eslintrc.js
|
||||
graphql-codegen.config.js
|
||||
|
|
57
README.md
57
README.md
|
@ -1,18 +1,25 @@
|
|||
# Accords-library.com
|
||||
|
||||
|
||||
[![Node.js CI](https://github.com/Accords-Library/accords-library.com/actions/workflows/node.js.yml/badge.svg?branch=main)](https://github.com/Accords-Library/accords-library.com/actions/workflows/node.js.yml)
|
||||
[![GitHub](https://img.shields.io/github/license/Accords-Library/accords-library.com?style=flat-square)](https://github.com/Accords-Library/accords-library.com/blob/main/LICENSE)
|
||||
![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/Accords-Library/accords-library.com?style=flat-square)
|
||||
|
||||
## Introduction
|
||||
|
||||
Accord’s Library is a fan-site that aims at gathering and archiving all of Yoko Taro’s work.
|
||||
Yoko Taro is a Japanese video game director and scenario writer. He is best-known for his work on the NieR and Drakengard (Drag-on Dragoon) franchises.
|
||||
|
||||
|
||||
## Technologies
|
||||
|
||||
#### [Back](https://github.com/Accords-Library/strapi.accords-library.com)
|
||||
#### [Content Management System](https://github.com/Accords-Library/strapi.accords-library.com)
|
||||
|
||||
- CMS: Stapi
|
||||
- CMS: [Stapi](https://strapi.io/)
|
||||
- GraphQL endpoint
|
||||
- Multilanguage support
|
||||
- Markdown format for the rich text fields
|
||||
- Use webhooks to notify the front-end and image processor of updates
|
||||
- Use webhooks to notify the front-end, search engine, and image processor of updates
|
||||
|
||||
#### [Image Processor](https://github.com/Accords-Library/img.accords-library.com)
|
||||
|
||||
|
@ -22,51 +29,81 @@
|
|||
- Large: 2048x2048, quality 80, .webp
|
||||
- Og: 512x512, quality 60, .jpg
|
||||
|
||||
#### [Search Engine](https://github.com/Accords-Library/search.accords-library.com)
|
||||
|
||||
- Search Engine: [Meilisearch](https://www.meilisearch.com/)
|
||||
|
||||
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
|
||||
|
||||
- Language: [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
- Framework: [Next.js 13](https://nextjs.org/) (React 18)
|
||||
|
||||
- Queries: [GraphQL Code Generator](https://www.graphql-code-generator.com/)
|
||||
|
||||
- Fetch the GraphQL schema from the GraphQL back-end endpoint
|
||||
- Read the operations and fragments stored as graphql files in the `src/graphql` folder
|
||||
- Automatically generates a typesafe ready to use SDK using [graphql-request](https://www.npmjs.com/package/graphql-request) as the GraphQL client
|
||||
- Markdown: [markdown-to-jsx](https://www.npmjs.com/package/markdown-to-jsx)
|
||||
- Support for arbitrary React Components and Component Props!
|
||||
|
||||
- Markdown
|
||||
- Use [Marked](https://www.npmjs.com/package/marked) to convert markdown to HTML (which is then sanitized using [DOMPurify](https://www.npmjs.com/package/isomorphic-dompurify))
|
||||
- Support for arbitrary React Components and Component Props using [markdown-to-jsx](https://www.npmjs.com/package/markdown-to-jsx)
|
||||
- Autogenerated multi-level table of content and anchor links for the different headers
|
||||
|
||||
- Styling: [Tailwind CSS](https://tailwindcss.com/)
|
||||
|
||||
- Support for [Material Symbols](https://fonts.google.com/icons)
|
||||
- Support for creating any arbitrary theming mode by swapping CSS variables
|
||||
- Support for Container Queries (media queries at the element level)
|
||||
- The website has a three-column layout, which turns into one-column + 2 toggleable side-menus if the screen is too narrow.
|
||||
- Check out our [Design System Showcase](https://accords-library.com/dev/showcase/design-system)
|
||||
|
||||
- State Management: [Jōtai](https://jotai.org/)
|
||||
|
||||
- Jōtai is a small-weighted library for atomic state management
|
||||
- Persistent app state using LocalStorage and SessionStorage
|
||||
|
||||
- Accessibility
|
||||
|
||||
- Gestures using [react-swipeable](https://www.npmjs.com/package/react-swipeable)
|
||||
- Keyboard hotkeys using [react-hotkeys-hook](https://www.npmjs.com/package/react-hotkeys-hook)
|
||||
- Support for light and dark mode with a manual switch and system's selected theme by default
|
||||
- Fonts can be swaped to [OpenDyslexic](https://www.npmjs.com/package/@fontsource/opendyslexic)
|
||||
|
||||
- Multilingual
|
||||
|
||||
- By default, use the browser's language as the main language
|
||||
- Fallback languages are used for content which are not available in the main language
|
||||
- Main and fallback languages can be ordered manually by the user
|
||||
- At the content level, the user can know which language is available
|
||||
- Furthermore, the user can temporary select another language then the one that was automatically selected
|
||||
|
||||
- UI Localizations
|
||||
|
||||
- The translated wordings use [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/) to include variables, plural, dates...
|
||||
- Use a custom ICU Typescript transformation script to provide type safety when formatting ICU wordings
|
||||
- Fallback to English if the working isn't available in one language
|
||||
|
||||
- SSG + ISR (Static Site Generation + Incremental Static Regeneration)
|
||||
|
||||
- The website is built before running in production
|
||||
- Performances are great, and possibility to deploy the app on a CDN
|
||||
- On-Demand ISR to continuously update the website when new content is added or existing content is modified/deleted
|
||||
- UI localizations are downloaded separetely into the `public/local-data` to avoid fetching the same static props for every page.
|
||||
|
||||
- SEO
|
||||
|
||||
- Good defaults for the metadata and OpenGraph properties
|
||||
- Each page can provide a custom thumbnail, title, description to be used
|
||||
- Automatic generation of the sitemap using [next-sitemap](https://www.npmjs.com/package/next-sitemap)
|
||||
- Data quality testing
|
||||
|
||||
- Data Quality Testing
|
||||
|
||||
- Data from the CMS is subject to a battery of tests (about 20 warning types and 40 error types) at build time
|
||||
- Each warning/error comes with a front-end link to the incriminating element, as well as a link to the CMS to fix it
|
||||
- Check for completeness, conformity, and integrity
|
||||
- Code quality and style
|
||||
|
||||
- Code Quality and Style
|
||||
|
||||
- React Strict Mode
|
||||
- [Eslint](https://www.npmjs.com/package/eslint) with [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import), [typescript-eslint](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin)
|
||||
|
@ -76,6 +113,12 @@
|
|||
- Other
|
||||
- Custom book reader based on [Okuma-Reader](https://github.com/DrMint/Okuma-Reader)
|
||||
- Custom lightbox using [react-zoom-pan-pinch](https://www.npmjs.com/package/react-zoom-pan-pinch)
|
||||
- Handle query params using [Zod](https://zod.dev/)
|
||||
- A special "Terminal" mode. Can you find it?
|
||||
|
||||
## Design
|
||||
|
||||
Check out our [Design System Showcase](https://accords-library.com/dev/showcase/design-system)!
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0"?>
|
||||
<minder version="1.14.0" parent-etag="565269084" etag="3671330291">
|
||||
<theme name="dark" label="Dark" index="-1"/>
|
||||
<styles>
|
||||
<style level="0" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="rounded" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="10" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="1" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="2" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="3" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="4" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="5" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="6" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="7" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="8" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="9" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<style level="10" isset="true" branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
</styles>
|
||||
<drawarea x="-706.6563110351558" y="37.12041219075536" scale="1.5"/>
|
||||
<images/>
|
||||
<nodes>
|
||||
<node id="0" posx="619.05682373046852" posy="496.07335408528627" width="181" height="56" side="left" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="638.05682373046852" posy="515.07335408528627" maxwidth="200">
|
||||
<text data="accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="1" posx="573.47979736328057" posy="193.53715006510404" width="228" height="56" side="right" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="592.47979736328057" posy="212.53715006510404" maxwidth="200">
|
||||
<text data="strapi.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="2" posx="479.06459554036411" posy="352.9875793457029" width="230" height="56" side="right" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="498.06459554036411" posy="371.9875793457029" maxwidth="200">
|
||||
<text data="watch.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="3" posx="930.07356770833326" posy="485.75804646809888" width="235" height="56" side="left" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="949.07356770833326" posy="504.75804646809888" maxwidth="200">
|
||||
<text data="search.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="4" posx="270.75532023111936" posy="469.50143432617188" width="213" height="56" side="left" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="289.75532023111936" posy="488.50143432617188" maxwidth="200">
|
||||
<text data="img.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="5" posx="723.3406372070308" posy="704.73269653320312" width="236" height="56" side="right" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="742.3406372070308" posy="723.73269653320312" maxwidth="200">
|
||||
<text data="umami.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="6" posx="438.2949829101554" posy="704.79434204101562" width="234" height="56" side="right" fold="false" treesize="56" layout="Horizontal" group="false">
|
||||
<style branchmargin="100" branchradius="25" linktype="straight" linkwidth="4" linkarrow="false" linkdash="solid" nodeborder="underlined" nodewidth="200" nodeborderwidth="4" nodefill="false" nodemargin="8" nodepadding="11" nodefont="Sans 11" nodemarkup="true"/>
|
||||
<nodename posx="457.2949829101554" posy="723.79434204101562" maxwidth="200">
|
||||
<text data="gallery.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
</nodes>
|
||||
<groups/>
|
||||
<connections>
|
||||
<connection from_id="3" to_id="1" drag_x="1008.9155680338538" drag_y="291.56668090820301" color="#813d9c">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>GraphQL queries</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="1" to_id="3" drag_x="952.30708821614598" drag_y="342.49227905273426">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>Webhook</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="2" to_id="1" drag_x="610.53983561197947" drag_y="303.31814575195307" color="#813d9c">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>GraphQL mutations</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="0" to_id="5" drag_x="771.31520589192746" drag_y="639.53558349609443">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>Sends events</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="4" to_id="0" drag_x="501.63850911458314" drag_y="577.36627197265648">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="150"/>
|
||||
<title>Provides the images</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="3" to_id="0" drag_x="887.70037841796875" drag_y="580.34930419921943">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="150"/>
|
||||
<title>Provides search results</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="0" to_id="1" drag_x="825.44006347656227" drag_y="376.72006225585938">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>GraphQL queries</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="0" to_id="6" drag_x="634.52079264322902" drag_y="640.60829671224042">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>Links to</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="4" to_id="1" drag_x="371.127197265625" drag_y="290.4098002115885" color="#813d9c">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>Python script</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="2" to_id="0" drag_x="616.25642903645803" drag_y="443.84891764322913">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="150"/>
|
||||
<title>Provides the videos</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="1" to_id="4" drag_x="411.43526204427064" drag_y="337.32072957356752">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>Webhook</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
</connections>
|
||||
<stickers/>
|
||||
</minder>
|
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
|
@ -10,9 +10,11 @@
|
|||
"@fontsource/share-tech-mono": "^4.5.9",
|
||||
"@fontsource/vollkorn": "^4.5.14",
|
||||
"@fontsource/zen-maru-gothic": "^4.5.16",
|
||||
"@formatjs/icu-messageformat-parser": "^2.1.14",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cuid": "^2.1.8",
|
||||
"intl-messageformat": "^10.2.5",
|
||||
"isomorphic-dompurify": "^0.26.0",
|
||||
"jotai": "^1.13.1",
|
||||
"markdown-to-jsx": "^7.1.8",
|
||||
|
@ -22,7 +24,7 @@
|
|||
"next": "^13.1.5",
|
||||
"nodemailer": "^6.9.0",
|
||||
"rc-slider": "^10.1.0",
|
||||
"react": "18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-swipeable": "^7.0.0",
|
||||
|
@ -1529,6 +1531,75 @@
|
|||
"resolved": "https://registry.npmjs.org/@fontsource/zen-maru-gothic/-/zen-maru-gothic-4.5.16.tgz",
|
||||
"integrity": "sha512-KM3z1IfKRF3p9nE2TEyVSbQUHhrpSh14cUZ8B6asjOzgzS8PXXyUDD+vmvptIswI3C/1tMQUBns8mzuub3lHZQ=="
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
|
||||
"integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract/node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.7.tgz",
|
||||
"integrity": "sha512-hPeM5LXUUjtCKPybWOUAWpv8lpja8Xz+uKprFPJcg5F2Rd+/bf1E0UUsLRpaAgOReAf5HMRtoIgv/UcyPICrTQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize/node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.14.tgz",
|
||||
"integrity": "sha512-0KqeVOb72losEhUW+59vhZGGd14s1f35uThfEMVKZHKLEObvJdFTiI3ZQwvTMUCzLEMxnS6mtnYPmG4mTvwd3Q==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.18",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser/node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz",
|
||||
"integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser/node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher/node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"node_modules/@graphql-codegen/cli": {
|
||||
"version": "2.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-2.16.4.tgz",
|
||||
|
@ -6962,6 +7033,22 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.2.5.tgz",
|
||||
"integrity": "sha512-AievYMN6WLLHwBeCTv4aRKG+w3ZNyZtkObwgsKk3Q7GNTq8zDRvDbJSBQkb2OPeVCcAKcIXvak9FF/bRNavoww==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/fast-memoize": "1.2.7",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.14",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat/node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
|
@ -11662,6 +11749,85 @@
|
|||
"resolved": "https://registry.npmjs.org/@fontsource/zen-maru-gothic/-/zen-maru-gothic-4.5.16.tgz",
|
||||
"integrity": "sha512-KM3z1IfKRF3p9nE2TEyVSbQUHhrpSh14cUZ8B6asjOzgzS8PXXyUDD+vmvptIswI3C/1tMQUBns8mzuub3lHZQ=="
|
||||
},
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
|
||||
"integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==",
|
||||
"requires": {
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/fast-memoize": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.7.tgz",
|
||||
"integrity": "sha512-hPeM5LXUUjtCKPybWOUAWpv8lpja8Xz+uKprFPJcg5F2Rd+/bf1E0UUsLRpaAgOReAf5HMRtoIgv/UcyPICrTQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.14.tgz",
|
||||
"integrity": "sha512-0KqeVOb72losEhUW+59vhZGGd14s1f35uThfEMVKZHKLEObvJdFTiI3ZQwvTMUCzLEMxnS6mtnYPmG4mTvwd3Q==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.18",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz",
|
||||
"integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-localematcher": {
|
||||
"version": "0.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@graphql-codegen/cli": {
|
||||
"version": "2.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-2.16.4.tgz",
|
||||
|
@ -15915,6 +16081,24 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"intl-messageformat": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.2.5.tgz",
|
||||
"integrity": "sha512-AievYMN6WLLHwBeCTv4aRKG+w3ZNyZtkObwgsKk3Q7GNTq8zDRvDbJSBQkb2OPeVCcAKcIXvak9FF/bRNavoww==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/fast-memoize": "1.2.7",
|
||||
"@formatjs/icu-messageformat-parser": "2.1.14",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 12499",
|
||||
"precommit": "npm run fetch-local-data && npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
||||
"precommit": "npm run fetch-local-data && npm run icu-to-ts && npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
||||
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
|
||||
"fetch-local-data": "npm run generate && esrun src/graphql/fetchLocalData.ts --esrun",
|
||||
"prebuild": "npm run fetch-local-data",
|
||||
"icu-to-ts": "esrun src/graphql/icuToTypescript.ts --icu",
|
||||
"prebuild": "npm run fetch-local-data && npm run icu-to-ts",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap --config next-sitemap.config.js",
|
||||
"start": "next start -p 12500",
|
||||
|
@ -21,9 +22,11 @@
|
|||
"@fontsource/share-tech-mono": "^4.5.9",
|
||||
"@fontsource/vollkorn": "^4.5.14",
|
||||
"@fontsource/zen-maru-gothic": "^4.5.16",
|
||||
"@formatjs/icu-messageformat-parser": "^2.1.14",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cuid": "^2.1.8",
|
||||
"intl-messageformat": "^10.2.5",
|
||||
"isomorphic-dompurify": "^0.26.0",
|
||||
"jotai": "^1.13.1",
|
||||
"markdown-to-jsx": "^7.1.8",
|
||||
|
@ -33,7 +36,7 @@
|
|||
"next": "^13.1.5",
|
||||
"nodemailer": "^6.9.0",
|
||||
"rc-slider": "^10.1.0",
|
||||
"react": "18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-swipeable": "^7.0.0",
|
||||
|
|
|
@ -13,16 +13,14 @@
|
|||
"wiki_short_description": "An encyclopedia for everything related to DrakeNieR",
|
||||
"chronicles_short_description": "Experience all events and content in chronological order",
|
||||
"news": "News",
|
||||
"merch": "Merch",
|
||||
"gallery": "Gallery",
|
||||
"archives": "Archives",
|
||||
"about_us": "About us",
|
||||
"licensing_notice": "This website’s content is made available under [CC-BY-SA](https://creativecommons.org/licenses/by-sa/4.0/) unless otherwise noted.",
|
||||
"copyright_notice": "Accord’s Library is not affiliated with or endorsed by SQUARE ENIX CO. LTD. All game assets and promotional materials belongs to © SQUARE ENIX CO. LTD.",
|
||||
"contents_description": "All the contents (textual, audio, and video) from the Library or other online sources.",
|
||||
"type": "Type",
|
||||
"category": "Category",
|
||||
"categories": "Categories",
|
||||
"type": "{ count, plural, =0 {No types} one {Type} other {Types} }",
|
||||
"category": "{ count, plural, =0 {No categories} one {Category} other {Categories} }",
|
||||
"size": "Size",
|
||||
"release_date": "Release date",
|
||||
"release_year": "Release year",
|
||||
|
@ -31,23 +29,17 @@
|
|||
"width": "Width",
|
||||
"height": "Height",
|
||||
"thickness": "Thickness",
|
||||
"subitem": "Subitem",
|
||||
"subitems": "Subitems",
|
||||
"subitem_of": "Subitem of",
|
||||
"variant": "Variant",
|
||||
"variants": "Variants",
|
||||
"variant_of": "Variant of",
|
||||
"subitem": "{ count, plural, =0 {No subitems} one {Subitem} other {Subitems} }",
|
||||
"variant": "{ count, plural, =0 {No variants} one {Variant} other {Variants} }",
|
||||
"summary": "Summary",
|
||||
"audio": "Audio",
|
||||
"video": "Video",
|
||||
"textual": "Textual",
|
||||
"game": "Game",
|
||||
"other": "Other",
|
||||
"return_to": "Return to",
|
||||
"left_to_right": "Left to right",
|
||||
"right_to_left": "Right to left",
|
||||
"page": "Page",
|
||||
"pages": "Pages",
|
||||
"page": "{ count, plural, =0 {No pages} one {Page} other {Pages} }",
|
||||
"page_order": "Page order",
|
||||
"binding": "Binding",
|
||||
"type_information": "Type information",
|
||||
|
@ -60,15 +52,12 @@
|
|||
"view_scans": "View scans",
|
||||
"paperback": "Paperback",
|
||||
"hardcover": "Hardcover",
|
||||
"languages": "Languages",
|
||||
"select_language": "Select a language",
|
||||
"language": "Language",
|
||||
"language": "{ count, plural, =0 {No languages} one {Language} other {Languages} }",
|
||||
"library_description": "A comprehensive list of all Yokoverse’s side materials (books, novellas, artbooks, stage plays, manga, drama CDs, and comics). For each, we provide photos, scans, and transcript of the content, information about what it is, when and how it was released, size, initial price…",
|
||||
"wiki_description": "An encyclopedia for everything related to DrakeNieR. Right now, we only have the Chronology but a lot more pages are planned to be released!",
|
||||
"chronicles_description": "Experience all events and content in chronological order.",
|
||||
"news_description": "News articles written by our Recorders! Here you will find announcements about new merch/items releases, guides, theories, unboxings, showcases...",
|
||||
"merch_description": "Harum ut consequatur a earum explicabo suscipit. Nostrum asperiores consectetur aperiam in ut sunt. Ipsa quibusdam et vel quam voluptas placeat. Qui est aliquam voluptatem. Tempora nisi exercitationem tempore sapiente expedita. Voluptas ut eaque nulla sunt ut dolor corrupti quos.",
|
||||
"gallery_description": "A fully tagged Danbooru-styled gallery with currently more than a thousand unique official artworks.",
|
||||
"archives_description": "Besides physical medias, we also archive digital contents such as websites, webpages, videos, and documents.",
|
||||
"about_us_description": "Find more information about the Accord's Library project in the following pages.",
|
||||
"page_not_found": "Oops! We’re having trouble finding this page",
|
||||
|
@ -77,8 +66,6 @@
|
|||
"show_subitems": "Show subitems",
|
||||
"show_primary_items": "Show primary items",
|
||||
"show_secondary_items": "Show secondary items",
|
||||
"no_type": "No type",
|
||||
"no_year": "No year",
|
||||
"order_by": "Order by",
|
||||
"group_by": "Group by",
|
||||
"select_option_sidebar": "Select one of the options in the sidebar",
|
||||
|
@ -109,20 +96,14 @@
|
|||
"translation_notice": "This content is a fan-translation",
|
||||
"source_language": "Source language",
|
||||
"pronouns": "Pronouns",
|
||||
"no_category": "No category",
|
||||
"item": "Item",
|
||||
"items": "Items",
|
||||
"item": "{ count, plural, =0 {No items} one {Item} other {Items} }",
|
||||
"content": "Content",
|
||||
"result": "Result",
|
||||
"results": "Results",
|
||||
"language_switch_message": "",
|
||||
"open_settings": "Open settings",
|
||||
"change_language": "Change language",
|
||||
"open_search": "Open search",
|
||||
"chronology": "Chronology",
|
||||
"accords_handbook": "Accord's Handbook",
|
||||
"legality": "Legality",
|
||||
"members": "Members",
|
||||
"sharing_policy": "Sharing Policy",
|
||||
"contact_us": "Contact us",
|
||||
"email": "Email",
|
||||
|
@ -155,7 +136,6 @@
|
|||
"only_display_unmarked_items": "Only display unmarked items",
|
||||
"display_all_items": "Display all items",
|
||||
"table_of_contents": "Table of Contents",
|
||||
"definition": "Definition",
|
||||
"no_results_message": "No results. You can try changing or resetting the search parameters.",
|
||||
"all": "All",
|
||||
"special_pages": "Special Pages",
|
||||
|
@ -193,7 +173,13 @@
|
|||
"most_popular": "Most popular",
|
||||
"shortest": "Shortest",
|
||||
"longest": "Longest",
|
||||
"search": "Search"
|
||||
"search": "Search",
|
||||
"showing_x_out_of_y_results": "Showing {x} out of {y} results",
|
||||
"return_to_x": "Return { x, select, undefined {} other {to {x}} }",
|
||||
"x_results": "{ x, plural, =0 {No results} one {# result} other {# results} }",
|
||||
"definition_x": "Definition {x}",
|
||||
"subitem_of_x": "Subitem of {x}",
|
||||
"variant_of_x": "Variant of {x}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -208,16 +194,14 @@
|
|||
"wiki_short_description": "Une encyclopédie pour tout l'univers DrakeNieR",
|
||||
"chronicles_short_description": "Parcourir tous les événements et les contenu dans l'ordre chronologique",
|
||||
"news": "News",
|
||||
"merch": "Merch",
|
||||
"gallery": "Galerie",
|
||||
"archives": "Archives",
|
||||
"about_us": "À propos",
|
||||
"licensing_notice": "Le contenu de ce site web est mis à disposition sous licence [CC-BY-SA](https://creativecommons.org/licenses/by-sa/4.0/), sauf indication contraire.",
|
||||
"copyright_notice": "Accord's Library n'est pas affiliée ni approuvée par SQUARE ENIX CO. LTD. Tous les contenus du jeu et les contenus promotionnel appartiennent à © SQUARE ENIX CO. LTD.",
|
||||
"contents_description": "",
|
||||
"type": "Type",
|
||||
"category": "Catégorie",
|
||||
"categories": "Catégories",
|
||||
"contents_description": "Tous les contenus (textuels, audio et vidéo) de la Bibliothèque ou d'autres sources en ligne.",
|
||||
"type": "{ count, plural, =0 {Pas de type} one {Type} other {Types} }",
|
||||
"category": "{ count, plural, =0 {Pas de catégorie} one {Catégorie} other {Catégories} }",
|
||||
"size": "Dimension",
|
||||
"release_date": "Date de sortie",
|
||||
"release_year": "Année de sortie",
|
||||
|
@ -226,23 +210,17 @@
|
|||
"width": "Largeur",
|
||||
"height": "Hauteur",
|
||||
"thickness": "Épaisseur",
|
||||
"subitem": "Sous-item",
|
||||
"subitems": "Sous-items",
|
||||
"subitem_of": "Sous-item de",
|
||||
"variant": "Variante",
|
||||
"variants": "Variantes",
|
||||
"variant_of": "Variante de",
|
||||
"subitem": "{ count, plural, =0 {Pas de sous-item} one {Sous-item} other {Sous-items} }",
|
||||
"variant": "{ count, plural, =0 {Pas de variante} one {Variante} other {Variantes} }",
|
||||
"summary": "Résumé",
|
||||
"audio": "Audio",
|
||||
"video": "Vidéo",
|
||||
"textual": "Textuel",
|
||||
"game": "Jeux",
|
||||
"other": "Autre",
|
||||
"return_to": "Retourner à ",
|
||||
"left_to_right": "Gauche à droite",
|
||||
"right_to_left": "Droite à gauche",
|
||||
"page": "Page",
|
||||
"pages": "Pages",
|
||||
"page": "{ count, plural, =0 {Aucune page} one {Page} other {Pages} }",
|
||||
"page_order": "Order des pages",
|
||||
"binding": "Brochure",
|
||||
"type_information": "Information de type",
|
||||
|
@ -255,25 +233,20 @@
|
|||
"view_scans": "Voir les scans",
|
||||
"paperback": "Broché",
|
||||
"hardcover": "Relié",
|
||||
"languages": "Langues",
|
||||
"select_language": "Séléctionner la langue",
|
||||
"language": "Langue",
|
||||
"library_description": "",
|
||||
"wiki_description": "",
|
||||
"chronicles_description": "",
|
||||
"language": "{ count, plural, =0 {Aucune langue} one {Langue} other {Langues} }",
|
||||
"library_description": "Une liste complète de tous les médias annexes du Yokoverse (livres, romans, artbooks, pièces de théâtre, mangas, CD de théâtre et bandes dessinées). Pour chacun, nous fournissons des photos, des scans et une transcription du contenu, des informations sur sa nature, quand il est sorti, sa taille, son prix initial...",
|
||||
"wiki_description": "Une encyclopédie pour tout ce qui concerne DrakeNieR. Pour l'instant, nous n'avons que la Chronologie mais beaucoup plus de pages sont prévues !",
|
||||
"chronicles_description": "Découvrez tous les événements et contenus de manière chronologique.",
|
||||
"news_description": "Articles d'actualité écrits par nos Recorders ! Vous trouverez ici des annonces sur les nouvelles sorties de merch/items, des guides, des théories, des unboxings, des showcases...",
|
||||
"merch_description": "Lorem ipsum",
|
||||
"gallery_description": "",
|
||||
"archives_description": "",
|
||||
"about_us_description": "",
|
||||
"archives_description": "Outre les supports physiques, nous archivons également les contenus numériques tels que les sites web, les pages web, les vidéos et les documents.",
|
||||
"about_us_description": "Vous trouverez plus d'informations sur le projet Accord's Library dans les pages suivantes.",
|
||||
"page_not_found": "Page introuvable",
|
||||
"default_description": "Accord's Library a pour but de rassembler et d'archiver l'ensemble des travaux de Yoko Taro. Yoko Taro est un réalisateur et scénariste de jeux vidéo japonais.",
|
||||
"name": "Nom",
|
||||
"show_subitems": "Afficher les sous-items",
|
||||
"show_primary_items": "Afficher les items primaires",
|
||||
"show_secondary_items": "Afficher les items secondaires",
|
||||
"no_type": "Pas de type",
|
||||
"no_year": "Pas d'année",
|
||||
"order_by": "Ordonné par",
|
||||
"group_by": "Groupé par",
|
||||
"select_option_sidebar": "Sélectionner l'une des options de la barre latérale",
|
||||
|
@ -287,7 +260,7 @@
|
|||
"player_name": "Nom du joueur",
|
||||
"currency": "Devise",
|
||||
"font": "Police d'écriture",
|
||||
"calculated": "calculé",
|
||||
"calculated": "Calculé",
|
||||
"status_incomplete": "Cette entrée n'est que partiellement traduite/transcrite.",
|
||||
"status_draft": "Cette entrée n'est qu'un brouillon. Cela signifie généralement qu'il s'agit d'un travail en cours. La traduction/transcription peut être médiocre et/ou auto-générée par ordinateur.",
|
||||
"status_review": "Cet entrée n'a pas encore été relue. Le contenu devrait néanmoins être correct.",
|
||||
|
@ -304,20 +277,14 @@
|
|||
"translation_notice": "Ceci est une traduction",
|
||||
"source_language": "Langue source",
|
||||
"pronouns": "Pronoms",
|
||||
"no_category": "Pas de categorie",
|
||||
"item": "Item",
|
||||
"items": "Items",
|
||||
"item": "{ count, plural, =0 {Aucun item} one {Item} other {Items} }",
|
||||
"content": "Content",
|
||||
"result": "Resultat",
|
||||
"results": "Résultats",
|
||||
"language_switch_message": "Ce contenu n'est pas disponible dans la langue actuellement sélectionnée. Vous pouvez sélectionner l'une des langues suivantes à la place :",
|
||||
"open_settings": "Ouvrir les paramètres",
|
||||
"change_language": "Changer de langue",
|
||||
"open_search": "Ouvrir le menu de recherche",
|
||||
"chronology": "Chronologie",
|
||||
"accords_handbook": "Le manuel de Accord",
|
||||
"legality": "Légalité",
|
||||
"members": "Membres",
|
||||
"sharing_policy": "Politique de partage",
|
||||
"contact_us": "Nous contacter",
|
||||
"email": "Email",
|
||||
|
@ -350,7 +317,6 @@
|
|||
"only_display_unmarked_items": "Seulement afficher les items non-marqués",
|
||||
"display_all_items": "Afficher tous les items",
|
||||
"table_of_contents": "Sommaire",
|
||||
"definition": "Definition",
|
||||
"no_results_message": "Aucun résultat. Vous pouvez essayer de modifier ou de réinitialiser les paramètres de recherche.",
|
||||
"all": "Tous",
|
||||
"special_pages": "Pages spéciales",
|
||||
|
@ -388,7 +354,13 @@
|
|||
"most_popular": "Moins populaires",
|
||||
"shortest": "Plus courtes",
|
||||
"longest": "Plus longues",
|
||||
"search": "Rechercher"
|
||||
"search": "Rechercher",
|
||||
"showing_x_out_of_y_results": "{x} résultats sur {y} affichés",
|
||||
"return_to_x": "Retour { x, select, undefined {} other {à {x}} }",
|
||||
"x_results": "{ x, plural, =0 {Pas de résultat} one {# résultat} other {# résultats} }",
|
||||
"definition_x": "Définition {x}",
|
||||
"subitem_of_x": "Sous-item de {x}",
|
||||
"variant_of_x": "Variante de {x}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -403,7 +375,6 @@
|
|||
"wiki_short_description": "ゲーム宇宙に関連するすべての百科事典です。",
|
||||
"chronicles_short_description": "すべてのイベントとコンテンツを時系列で体験できる",
|
||||
"news": "ニュース",
|
||||
"merch": "マーチ",
|
||||
"gallery": "ギャラリー",
|
||||
"archives": "アーカイブス",
|
||||
"about_us": "会社概要",
|
||||
|
@ -412,7 +383,6 @@
|
|||
"contents_description": "図書館や他のオンラインソースのすべてのコンテンツ(テキスト、オーディオ、ビデオ)。",
|
||||
"type": "タイプ",
|
||||
"category": "カテゴリー",
|
||||
"categories": "カテゴリー",
|
||||
"size": "サイズ",
|
||||
"release_date": "発売日",
|
||||
"release_year": "発売年",
|
||||
|
@ -422,22 +392,16 @@
|
|||
"height": "高さ",
|
||||
"thickness": "厚み",
|
||||
"subitem": "サブアイテム",
|
||||
"subitems": "サブアイテム",
|
||||
"subitem_of": "のサブアイテム",
|
||||
"variant": "バリアント",
|
||||
"variants": "バリアント",
|
||||
"variant_of": "のバリアント",
|
||||
"summary": "概要",
|
||||
"audio": "オーディオ",
|
||||
"video": "ビデオ",
|
||||
"textual": "テキスト",
|
||||
"game": "ゲーム",
|
||||
"other": "他",
|
||||
"return_to": "戻る",
|
||||
"left_to_right": "左から右へ",
|
||||
"right_to_left": "右から左へ",
|
||||
"page": "ページ",
|
||||
"pages": "ページ",
|
||||
"page_order": "ページ順序",
|
||||
"binding": "製本",
|
||||
"type_information": "タイプ情報",
|
||||
|
@ -450,15 +414,12 @@
|
|||
"view_scans": "スキャンを開放",
|
||||
"paperback": "ペーパーバック",
|
||||
"hardcover": "ハードカバー",
|
||||
"languages": "言語",
|
||||
"select_language": "言語を選択する",
|
||||
"language": "言語",
|
||||
"library_description": "ヨコベースの副教材(書籍、小説、画集、舞台劇、漫画、ドラマCD、コミック)を網羅したリストです。それぞれについて、写真、スキャン、内容の書き起こし、どんなものなのか、いつ、どのように発売されたのか、サイズ、初回価格...などの情報を掲載しています。",
|
||||
"wiki_description": "DrakeNieRに関連するすべての百科事典です。現在は年表のみですが、今後多くのページを公開予定です",
|
||||
"chronicles_description": "Accord's Libraryは、ヨーコ・タローの全作品を収集・保存することを目的としています。ヨーコ・タローは、日本のゲームディレクター、シナリオライターです。",
|
||||
"news_description": "レコーダーが書いたニュース記事です ここでは、新しい商品/アイテムのリリースに関するお知らせ、ガイド、セオリー、アンボックス、ショーケース...をご紹介しています。",
|
||||
"merch_description": "",
|
||||
"gallery_description": "",
|
||||
"archives_description": "",
|
||||
"about_us_description": "Accord's Libraryプロジェクトについては、以下のページで詳しくご紹介しています。",
|
||||
"page_not_found": "ページが見つかりません",
|
||||
|
@ -467,8 +428,6 @@
|
|||
"show_subitems": "サブアイテムをみせる",
|
||||
"show_primary_items": "一次のイテムをみせる",
|
||||
"show_secondary_items": "二次のイテムをみせる",
|
||||
"no_type": "タイプなし",
|
||||
"no_year": "年なし",
|
||||
"order_by": "注文する",
|
||||
"group_by": "グループ化する",
|
||||
"select_option_sidebar": "サイドバーのオプションを選択します",
|
||||
|
@ -499,20 +458,14 @@
|
|||
"translation_notice": "このコンテンツはファンによる翻訳です",
|
||||
"source_language": "ソース言語",
|
||||
"pronouns": "代名詞",
|
||||
"no_category": "カテゴリーなし",
|
||||
"item": "項目",
|
||||
"items": "項目",
|
||||
"content": "コンテンツ",
|
||||
"result": "結果",
|
||||
"results": "結果",
|
||||
"language_switch_message": null,
|
||||
"open_settings": "オープン設定",
|
||||
"change_language": "言語を変更する",
|
||||
"open_search": "オープンサーチ",
|
||||
"chronology": "年表",
|
||||
"accords_handbook": "アコードの手引き",
|
||||
"legality": "合法性",
|
||||
"members": "メンバー紹介",
|
||||
"sharing_policy": "共有ポリシー",
|
||||
"contact_us": "お問い合わせ",
|
||||
"email": "電子メール",
|
||||
|
@ -545,7 +498,6 @@
|
|||
"only_display_unmarked_items": "無印のアイテムのみ表示",
|
||||
"display_all_items": "すべての項目を表示する",
|
||||
"table_of_contents": "目次",
|
||||
"definition": "定義",
|
||||
"no_results_message": "結果が出ません。検索条件を変更またはリセットしてみてください。",
|
||||
"all": "すべて",
|
||||
"special_pages": "特設ページ",
|
||||
|
@ -583,7 +535,13 @@
|
|||
"most_popular": null,
|
||||
"shortest": null,
|
||||
"longest": null,
|
||||
"search": null
|
||||
"search": null,
|
||||
"showing_x_out_of_y_results": null,
|
||||
"return_to_x": null,
|
||||
"x_results": null,
|
||||
"definition_x": null,
|
||||
"subitem_of_x": null,
|
||||
"variant_of_x": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -598,7 +556,6 @@
|
|||
"wiki_short_description": "Una enciclopedia para todo lo relacionado con DrakeNieR",
|
||||
"chronicles_short_description": "Experimenta todos los eventos y contenidos en orden cronológico",
|
||||
"news": "Novedades",
|
||||
"merch": "Merch",
|
||||
"gallery": "Galería",
|
||||
"archives": "Archivos",
|
||||
"about_us": "Sobre nosotros",
|
||||
|
@ -607,7 +564,6 @@
|
|||
"contents_description": "Todo el contenido (textual, audio y video) de la Biblioteca u otras fuentes en línea.",
|
||||
"type": "Tipo",
|
||||
"category": "Categoría",
|
||||
"categories": "Categorías",
|
||||
"size": "Tamaño",
|
||||
"release_date": "Fecha de lanzamiento",
|
||||
"release_year": "Año de lanzamiento",
|
||||
|
@ -617,22 +573,16 @@
|
|||
"height": "Altura",
|
||||
"thickness": "Grosor",
|
||||
"subitem": "Sub-item",
|
||||
"subitems": "Sub-items",
|
||||
"subitem_of": "Sub-item de",
|
||||
"variant": "Variante",
|
||||
"variants": "Variantes",
|
||||
"variant_of": "Variante de",
|
||||
"summary": "Sumario",
|
||||
"audio": "Audio",
|
||||
"video": "Video",
|
||||
"textual": "Textual",
|
||||
"game": "Juego",
|
||||
"other": "Otros",
|
||||
"return_to": "Volver a",
|
||||
"left_to_right": "Izquierda a derecha",
|
||||
"right_to_left": "Derecha a izquierda",
|
||||
"page": "Página",
|
||||
"pages": "Páginas",
|
||||
"page_order": "Orden de las páginas",
|
||||
"binding": "Encuadernación",
|
||||
"type_information": "Tipo de información",
|
||||
|
@ -645,15 +595,12 @@
|
|||
"view_scans": "Ver escaneos",
|
||||
"paperback": "Tapa blanda",
|
||||
"hardcover": "Tapa dura",
|
||||
"languages": "Idiomas",
|
||||
"select_language": "Seleccionar idioma",
|
||||
"language": "Idioma",
|
||||
"library_description": "Una lista completa de todos los materiales complementarios de Yokoverse (libros, novelas, libros de arte, obras de teatro, manga, CDs novelizados y cómics). Para cada uno, proporcionamos fotos, escaneos y transcripciones del contenido, información sobre qué es, cuándo y cómo se ha publicado, tamaño, precio inicial...",
|
||||
"wiki_description": "Una enciclopedia para todo lo relacionado con DrakeNieR. En este momento, solo tenemos la Cronología, ¡pero muchas más páginas están planeadas para ser publicadas!",
|
||||
"chronicles_description": "",
|
||||
"news_description": "¡Nuevos artículos escritos por nuestros/as Archivistas! Aquí encontrarás anuncios sobre nuevos lanzamientos de merchandising/artículos, guías, teorías, unboxings, showcases...",
|
||||
"merch_description": "",
|
||||
"gallery_description": "Una galería completamente etiquetada de estilo Danbooru, actualmente con más de mil obras de arte oficiales únicas.",
|
||||
"archives_description": "",
|
||||
"about_us_description": "Encuentra más información sobre el proyecto de Accord's Library en las siguientes páginas.",
|
||||
"page_not_found": "Página no encontrada",
|
||||
|
@ -662,8 +609,6 @@
|
|||
"show_subitems": "Mostrar sub-items",
|
||||
"show_primary_items": "Mostrar items principales",
|
||||
"show_secondary_items": "Mostrar items secundarios",
|
||||
"no_type": "Ningún tipo",
|
||||
"no_year": "Ningún año",
|
||||
"order_by": "Ordenar por",
|
||||
"group_by": "Agrupar por",
|
||||
"select_option_sidebar": "Selecciona una de las opciones en la barra lateral",
|
||||
|
@ -671,7 +616,7 @@
|
|||
"settings": "Ajustes",
|
||||
"theme": "Tema",
|
||||
"light": "Claro",
|
||||
"auto": "Auto",
|
||||
"auto": "Automático",
|
||||
"dark": "Oscuro",
|
||||
"font_size": "Tamaño de la fuente",
|
||||
"player_name": "Nombre del jugador/a",
|
||||
|
@ -694,83 +639,76 @@
|
|||
"translation_notice": "Este contenido es una traducción de fans",
|
||||
"source_language": "Idioma original",
|
||||
"pronouns": "Pronombres",
|
||||
"no_category": "Ningún categoría",
|
||||
"item": null,
|
||||
"items": null,
|
||||
"content": null,
|
||||
"result": null,
|
||||
"results": null,
|
||||
"language_switch_message": null,
|
||||
"open_settings": null,
|
||||
"change_language": null,
|
||||
"open_search": null,
|
||||
"chronology": null,
|
||||
"accords_handbook": null,
|
||||
"legality": null,
|
||||
"members": null,
|
||||
"sharing_policy": null,
|
||||
"contact_us": null,
|
||||
"item": "Ítem",
|
||||
"content": "Contenido",
|
||||
"open_settings": "Abrir ajustes",
|
||||
"change_language": "Cambiar idioma",
|
||||
"open_search": "Abrir búsqueda",
|
||||
"chronology": "Cronología",
|
||||
"accords_handbook": "Manual de Accord",
|
||||
"legality": "Legalidad",
|
||||
"sharing_policy": "Política de Uso Compartido",
|
||||
"contact_us": "Contáctanos",
|
||||
"email": "Email",
|
||||
"email_gdpr_notice": "Solo usamos tu correo electrónico exclusivamente para contactarte en relación a tu solicitud. No compartimos este correo electrónico con nadie ni lo usamos para ningún otro propósito.",
|
||||
"message": null,
|
||||
"send": null,
|
||||
"message": "Mensaje",
|
||||
"send": "Enviar",
|
||||
"response_invalid_code": "El código de verificación es incorrecto.",
|
||||
"response_invalid_email": "¡Por favor, introduce una dirección de correo electrónico válida!",
|
||||
"response_email_success": "¡Gracias por contactarnos! Nos pondremos en contacto contigo en breve.",
|
||||
"always_show_info": null,
|
||||
"item_not_available": null,
|
||||
"primary_language": null,
|
||||
"secondary_language": null,
|
||||
"combine_related_contents": null,
|
||||
"previous_content": null,
|
||||
"followup_content": null,
|
||||
"videos": null,
|
||||
"always_show_info": "Siempre mostrar la información",
|
||||
"item_not_available": "Ítem no disponible",
|
||||
"primary_language": "Idioma primario",
|
||||
"secondary_language": "Idioma secundario",
|
||||
"combine_related_contents": "Combinar contenido relacionado",
|
||||
"previous_content": "Contenido anterior",
|
||||
"followup_content": "Contenido siguiente",
|
||||
"videos": "Vídeos",
|
||||
"view_on": null,
|
||||
"channel": null,
|
||||
"subscribers": null,
|
||||
"description": null,
|
||||
"available_at": null,
|
||||
"search_title": null,
|
||||
"want_it": null,
|
||||
"have_it": null,
|
||||
"source": null,
|
||||
"reset_all_filters": null,
|
||||
"only_display_items_i_have": null,
|
||||
"only_display_items_i_want": null,
|
||||
"only_display_unmarked_items": null,
|
||||
"display_all_items": null,
|
||||
"table_of_contents": null,
|
||||
"definition": null,
|
||||
"no_results_message": null,
|
||||
"all": null,
|
||||
"special_pages": null,
|
||||
"scan": null,
|
||||
"scanlation": null,
|
||||
"scanners": null,
|
||||
"cleaners": null,
|
||||
"typesetters": null,
|
||||
"notes": null,
|
||||
"cover": null,
|
||||
"tags": null,
|
||||
"no_source_warning": null,
|
||||
"copy_anchor_link": null,
|
||||
"anchor_link_copied": null,
|
||||
"folders": null,
|
||||
"empty_folder_message": null,
|
||||
"switch_to_grid_view": null,
|
||||
"switch_to_folder_view": null,
|
||||
"content_is_not_available": null,
|
||||
"paper_texture": null,
|
||||
"book_fold": null,
|
||||
"lighting": null,
|
||||
"side_pages": null,
|
||||
"shadow": null,
|
||||
"night_reader": null,
|
||||
"single_page_view": null,
|
||||
"double_page_view": null,
|
||||
"reset_all_options": null,
|
||||
"reading_layout": null,
|
||||
"quality": null,
|
||||
"channel": "Canal",
|
||||
"subscribers": "Suscriptores",
|
||||
"description": "Descripción",
|
||||
"available_at": "Disponible en",
|
||||
"search_title": "Buscar título",
|
||||
"want_it": "Lo quiero!",
|
||||
"have_it": "Lo tengo!",
|
||||
"source": "Fuente",
|
||||
"reset_all_filters": "Restablecer todos los filtros",
|
||||
"only_display_items_i_have": "Sólo mostrar lo que ya tengo",
|
||||
"only_display_items_i_want": "Sólo mostrar lo que quiero",
|
||||
"only_display_unmarked_items": "Sólo mostrar ítems sin marcación",
|
||||
"display_all_items": "Mostar todos los ítems",
|
||||
"table_of_contents": "Tabla de contenido",
|
||||
"no_results_message": "No hay resultados",
|
||||
"all": "Todos",
|
||||
"special_pages": "Páginas especiales",
|
||||
"scan": "Escaneos",
|
||||
"scanlation": "Escaneo/Traducción",
|
||||
"scanners": "Escaneadores",
|
||||
"cleaners": "Limpiadores",
|
||||
"typesetters": "Maquetadores",
|
||||
"notes": "Notas",
|
||||
"cover": "Portada",
|
||||
"tags": "Etiquetas",
|
||||
"no_source_warning": "No hay fuente",
|
||||
"copy_anchor_link": "Copiar enlace de anclaje",
|
||||
"anchor_link_copied": "Enlace de anclaje copiado",
|
||||
"folders": "Carpetas",
|
||||
"empty_folder_message": "Carpeta vacía",
|
||||
"switch_to_grid_view": "Cambiar a vista de retícula",
|
||||
"switch_to_folder_view": "Cambiar a vista de carpeta",
|
||||
"content_is_not_available": "Contenido no disponible",
|
||||
"paper_texture": "Papel texturizado",
|
||||
"book_fold": "Tapa dura",
|
||||
"lighting": "Iluminación",
|
||||
"side_pages": "Contraportada",
|
||||
"shadow": "Sombra",
|
||||
"night_reader": "Lector nocturno",
|
||||
"single_page_view": "Vista de página única",
|
||||
"double_page_view": "Vista de página doble",
|
||||
"reset_all_options": "Restablecer todas las opciones",
|
||||
"reading_layout": "Disposición de lectura",
|
||||
"quality": "Calidad",
|
||||
"only_unavailable_videos": null,
|
||||
"oldest": null,
|
||||
"newest": null,
|
||||
|
@ -778,7 +716,13 @@
|
|||
"most_popular": null,
|
||||
"shortest": null,
|
||||
"longest": null,
|
||||
"search": null
|
||||
"search": null,
|
||||
"showing_x_out_of_y_results": null,
|
||||
"return_to_x": null,
|
||||
"x_results": null,
|
||||
"definition_x": null,
|
||||
"subitem_of_x": null,
|
||||
"variant_of_x": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -793,7 +737,6 @@
|
|||
"wiki_short_description": "Uma enciclopédia com tudo relacionado a DrakeNieR",
|
||||
"chronicles_short_description": "Explore as crônicas de DrakeNieR em ordem cronológica.",
|
||||
"news": "Notícias",
|
||||
"merch": "Mercadorias",
|
||||
"gallery": "Galeria",
|
||||
"archives": "Arquivos",
|
||||
"about_us": "Sobre Nós",
|
||||
|
@ -802,7 +745,6 @@
|
|||
"contents_description": "",
|
||||
"type": "Tipo",
|
||||
"category": "Categoria",
|
||||
"categories": "Categorias",
|
||||
"size": "Tamanho",
|
||||
"release_date": "Dia de lançamento",
|
||||
"release_year": "Ano de lançamento",
|
||||
|
@ -812,22 +754,16 @@
|
|||
"height": "Altura",
|
||||
"thickness": "Grossura",
|
||||
"subitem": "Subitem",
|
||||
"subitems": "Subitens",
|
||||
"subitem_of": "Subitem de",
|
||||
"variant": "Variante",
|
||||
"variants": "Variantes",
|
||||
"variant_of": "Variante de",
|
||||
"summary": "Sumário",
|
||||
"audio": "Audio",
|
||||
"video": "Video",
|
||||
"textual": "Textos",
|
||||
"game": "Jogos",
|
||||
"other": "Outros",
|
||||
"return_to": "Voltar para",
|
||||
"left_to_right": "Esquerda para direita",
|
||||
"right_to_left": "Direita para esquerda",
|
||||
"page": "Página",
|
||||
"pages": "Páginas",
|
||||
"page_order": "Ordem de páginas",
|
||||
"binding": "Encadernação",
|
||||
"type_information": "Informação do tipo",
|
||||
|
@ -840,15 +776,12 @@
|
|||
"view_scans": "Ver scans",
|
||||
"paperback": "Brochura",
|
||||
"hardcover": "Capa dura",
|
||||
"languages": "Línguas",
|
||||
"select_language": "Selecionar língua",
|
||||
"language": "Língua",
|
||||
"library_description": "",
|
||||
"wiki_description": null,
|
||||
"chronicles_description": null,
|
||||
"news_description": "",
|
||||
"merch_description": "",
|
||||
"gallery_description": "",
|
||||
"archives_description": "",
|
||||
"about_us_description": "",
|
||||
"page_not_found": "Página não encontrada",
|
||||
|
@ -857,8 +790,6 @@
|
|||
"show_subitems": "Mostrar subitens",
|
||||
"show_primary_items": "Mostrar itens primários",
|
||||
"show_secondary_items": "Mostrar itens secundários",
|
||||
"no_type": "Sem tipo",
|
||||
"no_year": "Sem ano",
|
||||
"order_by": "Ordenar por",
|
||||
"group_by": "Agrupar por",
|
||||
"select_option_sidebar": "Selecione uma opção na aba lateral",
|
||||
|
@ -889,20 +820,14 @@
|
|||
"translation_notice": "Este conteúdo é uma tradução de fã.",
|
||||
"source_language": "Língua original",
|
||||
"pronouns": "Pronomes",
|
||||
"no_category": "Sem Categoria",
|
||||
"item": "Item",
|
||||
"items": "Itens",
|
||||
"content": "Conteúdo",
|
||||
"result": "Resultado",
|
||||
"results": "Resultados",
|
||||
"language_switch_message": "Este conteúdo não está disponível na língua selecionada. Você pode escolher uma das seguintes línguas:",
|
||||
"open_settings": "Abrir configurações",
|
||||
"change_language": "Mudar língua",
|
||||
"open_search": "Abrir pesquisa",
|
||||
"chronology": "Cronologia",
|
||||
"accords_handbook": "Livro de mão da Accord",
|
||||
"legality": "Legalidade",
|
||||
"members": "Membros",
|
||||
"sharing_policy": "Política de compartilhamento",
|
||||
"contact_us": "Fale conosco",
|
||||
"email": null,
|
||||
|
@ -935,7 +860,6 @@
|
|||
"only_display_unmarked_items": null,
|
||||
"display_all_items": null,
|
||||
"table_of_contents": null,
|
||||
"definition": null,
|
||||
"no_results_message": null,
|
||||
"all": null,
|
||||
"special_pages": null,
|
||||
|
@ -973,7 +897,13 @@
|
|||
"most_popular": null,
|
||||
"shortest": null,
|
||||
"longest": null,
|
||||
"search": null
|
||||
"search": null,
|
||||
"showing_x_out_of_y_results": null,
|
||||
"return_to_x": null,
|
||||
"x_results": null,
|
||||
"definition_x": null,
|
||||
"subitem_of_x": null,
|
||||
"variant_of_x": null
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { Ico } from "./Ico";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -17,10 +16,10 @@ interface Props {
|
|||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const AnchorShare = ({ id, className }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<ToolTip content={langui.copy_anchor_link} trigger="mouseenter" className="text-sm">
|
||||
<ToolTip content={langui.anchor_link_copied} trigger="click" className="text-sm">
|
||||
<ToolTip content={format("copy_anchor_link")} trigger="mouseenter" className="text-sm">
|
||||
<ToolTip content={format("anchor_link_copied")} trigger="click" className="text-sm">
|
||||
<Ico
|
||||
icon="link"
|
||||
className={cJoin("cursor-pointer transition-colors hover:text-dark", className)}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
|
|||
import { Ids } from "types/ids";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter, useAtomPair } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -48,7 +49,7 @@ export const AppLayout = ({
|
|||
const [isMainPanelOpened, setMainPanelOpened] = useAtomPair(atoms.layout.mainPanelOpened);
|
||||
const isMenuGesturesEnabled = useAtomGetter(atoms.layout.menuGesturesEnabled);
|
||||
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
const isScreenAtLeastXs = useAtomGetter(atoms.containerQueries.isScreenAtLeastXs);
|
||||
|
@ -149,7 +150,7 @@ export const AppLayout = ({
|
|||
{isDefined(contentPanel) ? (
|
||||
contentPanel
|
||||
) : (
|
||||
<ContentPlaceholder message={langui.select_option_sidebar ?? ""} icon={"chevron_left"} />
|
||||
<ContentPlaceholder message={format("select_option_sidebar")} icon={"chevron_left"} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { PageSelector } from "components/Inputs/PageSelector";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { isUndefined } from "helpers/asserts";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { Ids } from "types/ids";
|
||||
|
||||
|
@ -48,14 +49,14 @@ export const Paginator = ({
|
|||
|
||||
const DefaultRenderWhenEmpty = () => {
|
||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<div className="grid h-full place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
{is3ColumnsLayout && <Ico icon="chevron_left" className="!text-[300%]" />}
|
||||
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
||||
<p className="max-w-xs text-2xl">{format("no_results_message")}</p>
|
||||
{!is3ColumnsLayout && <Ico icon="chevron_right" className="!text-[300%]" />}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,8 +3,7 @@ import { ToolTip } from "components/ToolTip";
|
|||
import { LibraryItemUserStatus } from "types/types";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useLibraryItemUserStatus } from "hooks/useLibraryItemUserStatus";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -20,7 +19,7 @@ interface Props {
|
|||
|
||||
export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
||||
const { libraryItemUserStatus, setLibraryItemUserStatus } = useLibraryItemUserStatus();
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -28,10 +27,10 @@ export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
|||
"flex flex-row flex-wrap place-content-center place-items-center",
|
||||
cIf(expand, "gap-4", "gap-2")
|
||||
)}>
|
||||
<ToolTip content={langui.want_it} disabled={expand}>
|
||||
<ToolTip content={format("want_it")} disabled={expand}>
|
||||
<Button
|
||||
icon="favorite"
|
||||
text={expand ? langui.want_it : undefined}
|
||||
text={expand ? format("want_it") : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
@ -46,10 +45,10 @@ export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.have_it} disabled={expand}>
|
||||
<ToolTip content={format("have_it")} disabled={expand}>
|
||||
<Button
|
||||
icon="back_hand"
|
||||
text={expand ? langui.have_it : undefined}
|
||||
text={expand ? format("have_it") : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -15,6 +15,7 @@ import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -225,7 +226,7 @@ export const TableOfContents = ({
|
|||
title,
|
||||
horizontalLine = false,
|
||||
}: TableOfContentsProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||
|
||||
return (
|
||||
|
@ -233,7 +234,7 @@ export const TableOfContents = ({
|
|||
{toc.children.length > 0 && (
|
||||
<>
|
||||
{horizontalLine && <HorizontalLine />}
|
||||
<h3 className="text-xl">{langui.table_of_contents}</h3>
|
||||
<h3 className="text-xl">{format("table_of_contents")}</h3>
|
||||
<div className="max-w-[14.5rem] text-left">
|
||||
<p
|
||||
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
|
|||
import { isUndefined } from "helpers/asserts";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -22,7 +23,7 @@ interface Props {
|
|||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||
|
||||
return (
|
||||
|
@ -31,7 +32,7 @@ export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props):
|
|||
(!is3ColumnsLayout && displayOnlyOn === "1ColumnLayout") ||
|
||||
isUndefined(displayOnlyOn)) && (
|
||||
<div className={className}>
|
||||
<Button href={href} text={`${langui.return_to} ${title}`} icon="navigate_before" />
|
||||
<Button href={href} text={format("return_to_x", { x: title })} icon="navigate_before" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ColoredSvg } from "components/ColoredSvg";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter, useAtomPair, useAtomSetter } from "helpers/atoms";
|
||||
import { Markdawn } from "components/Markdown/Markdawn";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -18,7 +19,7 @@ import { Markdawn } from "components/Markdown/Markdawn";
|
|||
|
||||
export const MainPanel = (): JSX.Element => {
|
||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const [isMainPanelReduced, setMainPanelReduced] = useAtomPair(atoms.layout.mainPanelReduced);
|
||||
const setSettingsOpened = useAtomSetter(atoms.layout.settingsOpened);
|
||||
const setSearchOpened = useAtomSetter(atoms.layout.searchOpened);
|
||||
|
@ -72,7 +73,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
cIf(isMainPanelReduced && is3ColumnsLayout, "flex-col gap-3", "flex-row")
|
||||
)}>
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
||||
content={<h3 className="text-2xl">{format("open_settings")}</h3>}
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
@ -83,7 +84,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
/>
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_search}</h3>}
|
||||
content={<h3 className="text-2xl">{format("open_search")}</h3>}
|
||||
placement={isMainPanelReduced ? "right" : "top"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
@ -102,32 +103,32 @@ export const MainPanel = (): JSX.Element => {
|
|||
<NavOption
|
||||
url="/library"
|
||||
icon="auto_stories"
|
||||
title={langui.library}
|
||||
subtitle={langui.library_short_description}
|
||||
title={format("library")}
|
||||
subtitle={format("library_short_description")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/contents"
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
subtitle={langui.contents_short_description}
|
||||
title={format("contents")}
|
||||
subtitle={format("contents_short_description")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/wiki"
|
||||
icon="travel_explore"
|
||||
title={langui.wiki}
|
||||
subtitle={langui.wiki_short_description}
|
||||
title={format("wiki")}
|
||||
subtitle={format("wiki_short_description")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/chronicles"
|
||||
icon="schedule"
|
||||
title={langui.chronicles}
|
||||
subtitle={langui.chronicles_short_description}
|
||||
title={format("chronicles")}
|
||||
subtitle={format("chronicles_short_description")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
|
@ -136,7 +137,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
<NavOption
|
||||
url="/news"
|
||||
icon="newspaper"
|
||||
title={langui.news}
|
||||
title={format("news")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
|
@ -144,7 +145,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
<NavOption
|
||||
url="/merch"
|
||||
icon="store"
|
||||
title={langui.merch}
|
||||
title={format("merch")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
*/}
|
||||
|
@ -152,30 +153,30 @@ export const MainPanel = (): JSX.Element => {
|
|||
<NavOption
|
||||
url="https://gallery.accords-library.com/posts/"
|
||||
icon="perm_media"
|
||||
title={langui.gallery}
|
||||
title={format("gallery")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/archives"
|
||||
icon="save"
|
||||
title={langui.archives}
|
||||
title={format("archives")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
url="/about-us"
|
||||
icon="info"
|
||||
title={langui.about_us}
|
||||
title={format("about_us")}
|
||||
reduced={isMainPanelReduced && is3ColumnsLayout}
|
||||
/>
|
||||
|
||||
{(!isMainPanelReduced || !is3ColumnsLayout) && <HorizontalLine />}
|
||||
|
||||
<div className={cJoin("text-center", cIf(isMainPanelReduced && is3ColumnsLayout, "hidden"))}>
|
||||
{isDefinedAndNotEmpty(langui.licensing_notice) && (
|
||||
{isDefinedAndNotEmpty(format("licensing_notice")) && (
|
||||
<p>
|
||||
<Markdawn text={langui.licensing_notice} />
|
||||
<Markdawn text={format("licensing_notice")} />
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 mb-8 grid place-content-center">
|
||||
|
@ -199,9 +200,9 @@ export const MainPanel = (): JSX.Element => {
|
|||
/>
|
||||
</Link>
|
||||
</div>
|
||||
{isDefinedAndNotEmpty(langui.copyright_notice) && (
|
||||
{isDefinedAndNotEmpty(format("copyright_notice")) && (
|
||||
<p>
|
||||
<Markdawn text={langui.copyright_notice} />
|
||||
<Markdawn text={format("copyright_notice")} />
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-12 mb-4 grid h-4 grid-flow-col place-content-center gap-8">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { MaterialSymbol } from "material-symbols";
|
|||
import { Popup } from "components/Containers/Popup";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomPair, useAtomGetter } from "helpers/atoms";
|
||||
import { useAtomPair } from "helpers/atoms";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { PreviewCard, TranslatedPreviewCard } from "components/PreviewCard";
|
||||
|
@ -20,6 +20,7 @@ import { getVideoThumbnailURL } from "helpers/videos";
|
|||
import { UpPressable } from "components/Containers/UpPressable";
|
||||
import { prettyItemSubType, prettySlug } from "helpers/formatters";
|
||||
import { Ico } from "components/Ico";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -36,7 +37,7 @@ const SEARCH_LIMIT = 8;
|
|||
export const SearchPopup = (): JSX.Element => {
|
||||
const [isSearchOpened, setSearchOpened] = useAtomPair(atoms.layout.searchOpened);
|
||||
const [query, setQuery] = useState("");
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const [libraryItems, setLibraryItems] = useState<CustomSearchResponse<MeiliLibraryItem>>();
|
||||
const [contents, setContents] = useState<CustomSearchResponse<MeiliContent>>();
|
||||
const [videos, setVideos] = useState<CustomSearchResponse<MeiliVideo>>();
|
||||
|
@ -178,14 +179,14 @@ export const SearchPopup = (): JSX.Element => {
|
|||
fillViewport>
|
||||
<h2 className="inline-flex place-items-center gap-2 text-2xl">
|
||||
<Ico icon="search" isFilled />
|
||||
{langui.search}
|
||||
{format("search")}
|
||||
</h2>
|
||||
<TextInput onChange={setQuery} value={query} placeholder={langui.search_title} />
|
||||
<TextInput onChange={setQuery} value={query} placeholder={format("search_title")} />
|
||||
|
||||
<div className="flex w-full flex-wrap gap-12 gap-x-16">
|
||||
{isDefined(libraryItems) && (
|
||||
<SearchResultSection
|
||||
title={langui.library}
|
||||
title={format("library")}
|
||||
icon="auto_stories"
|
||||
href={`/library?page=1&query=${query}\
|
||||
&sort=0&primary=true&secondary=true&subitems=true&status=all`}
|
||||
|
@ -232,7 +233,7 @@ export const SearchPopup = (): JSX.Element => {
|
|||
|
||||
{isDefined(contents) && (
|
||||
<SearchResultSection
|
||||
title={langui.contents}
|
||||
title={format("contents")}
|
||||
icon="workspaces"
|
||||
href={`/contents/all?page=1&query=${query}&sort=0`}
|
||||
totalHits={contents.estimatedTotalHits}>
|
||||
|
@ -276,7 +277,7 @@ export const SearchPopup = (): JSX.Element => {
|
|||
|
||||
{isDefined(wikiPages) && (
|
||||
<SearchResultSection
|
||||
title={langui.wiki}
|
||||
title={format("wiki")}
|
||||
icon="travel_explore"
|
||||
href={`/wiki?page=1&query=${query}`}
|
||||
totalHits={wikiPages.estimatedTotalHits}>
|
||||
|
@ -327,7 +328,7 @@ export const SearchPopup = (): JSX.Element => {
|
|||
|
||||
{isDefined(posts) && (
|
||||
<SearchResultSection
|
||||
title={langui.news}
|
||||
title={format("news")}
|
||||
icon="newspaper"
|
||||
href={`/news?page=1&query=${query}`}
|
||||
totalHits={posts.estimatedTotalHits}>
|
||||
|
@ -369,7 +370,7 @@ export const SearchPopup = (): JSX.Element => {
|
|||
|
||||
{isDefined(videos) && (
|
||||
<SearchResultSection
|
||||
title={langui.videos}
|
||||
title={format("videos")}
|
||||
icon="movie"
|
||||
href={`/archives/videos?page=1&query=${query}&sort=1&gone=`}
|
||||
totalHits={videos.estimatedTotalHits}>
|
||||
|
@ -424,26 +425,30 @@ const SearchResultSection = ({
|
|||
href,
|
||||
totalHits,
|
||||
children,
|
||||
}: SearchResultSectionProps) => (
|
||||
<>
|
||||
{isDefined(totalHits) && totalHits > 0 && (
|
||||
<div>
|
||||
<div className="mb-6 grid place-content-start">
|
||||
<UpPressable
|
||||
className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4"
|
||||
href={href}>
|
||||
<Ico icon={icon} className="!text-3xl" isFilled />
|
||||
<div>
|
||||
<p className="font-headers text-lg">{title}</p>
|
||||
{isDefined(totalHits) && totalHits > SEARCH_LIMIT && (
|
||||
/* TODO: Langui */
|
||||
<p className="text-sm">{`(showing ${SEARCH_LIMIT} out of ${totalHits} results)`}</p>
|
||||
)}
|
||||
</div>
|
||||
</UpPressable>
|
||||
}: SearchResultSectionProps) => {
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<>
|
||||
{isDefined(totalHits) && totalHits > 0 && (
|
||||
<div>
|
||||
<div className="mb-6 grid place-content-start">
|
||||
<UpPressable
|
||||
className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4"
|
||||
href={href}>
|
||||
<Ico icon={icon} className="!text-3xl" isFilled />
|
||||
<div>
|
||||
<p className="font-headers text-lg">{title}</p>
|
||||
{isDefined(totalHits) && totalHits > SEARCH_LIMIT && (
|
||||
<p className="text-sm">
|
||||
({format("showing_x_out_of_y_results", { x: SEARCH_LIMIT, y: totalHits })})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</UpPressable>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ import { atoms } from "contexts/atoms";
|
|||
import { useAtomGetter, useAtomPair } from "helpers/atoms";
|
||||
import { ThemeMode } from "contexts/settings";
|
||||
import { Ico } from "components/Ico";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
export const SettingsPopup = (): JSX.Element => {
|
||||
const [preferredLanguages, setPreferredLanguages] = useAtomPair(
|
||||
|
@ -27,7 +28,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
const [themeMode, setThemeMode] = useAtomPair(atoms.settings.themeMode);
|
||||
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
|
@ -52,7 +53,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
}}>
|
||||
<h2 className="inline-flex place-items-center gap-2 text-2xl">
|
||||
<Ico icon="discover_tune" isFilled />
|
||||
{langui.settings}
|
||||
{format("settings")}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
|
@ -62,7 +63,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
)}>
|
||||
{router.locales && (
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.languages}</h3>
|
||||
<h3 className="text-xl">{format("language", { count: preferredLanguages.length })}</h3>
|
||||
{preferredLanguages.length > 0 && (
|
||||
<OrderableList
|
||||
items={preferredLanguages.map((locale) => ({
|
||||
|
@ -72,11 +73,11 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
insertLabels={[
|
||||
{
|
||||
insertAt: 0,
|
||||
name: langui.primary_language ?? "Primary language",
|
||||
name: format("primary_language"),
|
||||
},
|
||||
{
|
||||
insertAt: 1,
|
||||
name: langui.secondary_language ?? "Secondary languages",
|
||||
name: format("secondary_language"),
|
||||
},
|
||||
]}
|
||||
onChange={(items) => {
|
||||
|
@ -94,7 +95,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
cIf(!is1ColumnLayout, "grid-cols-2")
|
||||
)}>
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.theme}</h3>
|
||||
<h3 className="text-xl">{format("theme")}</h3>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
|
@ -103,7 +104,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
sendAnalytics("Settings", "Change theme (light)");
|
||||
},
|
||||
active: themeMode === ThemeMode.Light,
|
||||
text: langui.light,
|
||||
text: format("light"),
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
|
@ -111,7 +112,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
sendAnalytics("Settings", "Change theme (auto)");
|
||||
},
|
||||
active: themeMode === ThemeMode.Auto,
|
||||
text: langui.auto,
|
||||
text: format("auto"),
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
|
@ -119,14 +120,14 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
sendAnalytics("Settings", "Change theme (dark)");
|
||||
},
|
||||
active: themeMode === ThemeMode.Dark,
|
||||
text: langui.dark,
|
||||
text: format("dark"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.currency}</h3>
|
||||
<h3 className="text-xl">{format("currency")}</h3>
|
||||
<div>
|
||||
<Select
|
||||
options={currencyOptions}
|
||||
|
@ -144,7 +145,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.font_size}</h3>
|
||||
<h3 className="text-xl">{format("font_size")}</h3>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
|
@ -185,7 +186,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.font}</h3>
|
||||
<h3 className="text-xl">{format("font")}</h3>
|
||||
<div className="grid gap-2">
|
||||
<Button
|
||||
active={!isDyslexic}
|
||||
|
@ -209,7 +210,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.player_name}</h3>
|
||||
<h3 className="text-xl">{format("player_name")}</h3>
|
||||
<TextInput
|
||||
placeholder="<player>"
|
||||
className="w-48"
|
||||
|
|
|
@ -11,11 +11,9 @@ import { ThumbnailHeader } from "./ThumbnailHeader";
|
|||
import { ToolTip } from "./ToolTip";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { PostWithTranslations } from "types/types";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { filterHasAttributes } from "helpers/asserts";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -50,7 +48,8 @@ export const PostPage = ({
|
|||
displayTitle = true,
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format, formatStatusDescription } = useFormat();
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: post.translations,
|
||||
languageExtractor: useCallback(
|
||||
|
@ -79,10 +78,10 @@ export const PostPage = ({
|
|||
|
||||
{selectedTranslation && (
|
||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<p className="font-headers font-bold">{format("status")}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||
content={formatStatusDescription(selectedTranslation.status)}
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.status} />
|
||||
</ToolTip>
|
||||
|
|
|
@ -6,8 +6,7 @@ import { Chip } from "components/Chip";
|
|||
import { RecorderChipFragment } from "graphql/generated";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { filterHasAttributes } from "helpers/asserts";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -22,7 +21,7 @@ interface Props {
|
|||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
|
||||
return (
|
||||
<ToolTip
|
||||
|
@ -40,7 +39,7 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
|||
<h3 className=" text-2xl">{recorder.username}</h3>
|
||||
{recorder.languages?.data && recorder.languages.data.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>{langui.languages}:</p>
|
||||
<p>{format("language", { count: recorder.languages.data.length })}:</p>
|
||||
{filterHasAttributes(recorder.languages.data, ["attributes"] as const).map(
|
||||
(language) => (
|
||||
<Fragment key={language.__typename}>
|
||||
|
@ -52,7 +51,7 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
|||
)}
|
||||
{recorder.pronouns && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>{langui.pronouns}:</p>
|
||||
<p>{format("pronouns")}:</p>
|
||||
<Chip text={recorder.pronouns} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
|||
import { Ids } from "types/ids";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
interface Group<T> {
|
||||
name: string;
|
||||
|
@ -71,7 +72,7 @@ export const SmartList = <T,>({
|
|||
className,
|
||||
}: Props<T>): JSX.Element => {
|
||||
const [page, setPage] = useState(1);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
|
||||
useEffect(() => setPage(1), [searchingTerm, groupingFunction, groupSortingFunction]);
|
||||
|
||||
|
@ -180,13 +181,7 @@ export const SmartList = <T,>({
|
|||
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
||||
first-of-type:pt-0">
|
||||
{group.name}
|
||||
<Chip
|
||||
text={`${group.totalCount} ${
|
||||
group.items.length <= 1
|
||||
? langui.result?.toLowerCase() ?? ""
|
||||
: langui.results?.toLowerCase() ?? ""
|
||||
}`}
|
||||
/>
|
||||
<Chip text={format("x_results", { x: group.totalCount })} />
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
|
@ -222,15 +217,15 @@ export const SmartList = <T,>({
|
|||
*/
|
||||
|
||||
const DefaultRenderWhenEmpty = () => {
|
||||
const { format } = useFormat();
|
||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
return (
|
||||
<div className="grid h-full place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
{is3ColumnsLayout && <Ico icon="chevron_left" className="!text-[300%]" />}
|
||||
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
||||
<p className="max-w-xs text-2xl">{format("no_results_message")}</p>
|
||||
{!is3ColumnsLayout && <Ico icon="chevron_right" className="!text-[300%]" />}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ImageQuality } from "helpers/img";
|
|||
import { filterHasAttributes } from "helpers/asserts";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -42,7 +43,7 @@ export const ThumbnailHeader = ({
|
|||
description,
|
||||
languageSwitcher,
|
||||
}: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const { showLightBox } = useAtomGetter(atoms.lightBox);
|
||||
|
||||
return (
|
||||
|
@ -72,7 +73,7 @@ export const ThumbnailHeader = ({
|
|||
<div className="flew-wrap flex flex-row place-content-center gap-8">
|
||||
{type?.data?.attributes && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.type}</h3>
|
||||
<h3 className="text-xl">{format("type", { count: 1 })}</h3>
|
||||
<div className="flex flex-row flex-wrap">
|
||||
<Chip
|
||||
text={
|
||||
|
@ -85,7 +86,7 @@ export const ThumbnailHeader = ({
|
|||
|
||||
{categories && categories.data.length > 0 && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<h3 className="text-xl">{format("category", { count: categories.data.length })}</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(categories.data, ["attributes", "id"] as const).map(
|
||||
(category) => (
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { useCallback } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { ContentStatus, useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -21,7 +21,7 @@ interface Props {
|
|||
translations: {
|
||||
language: string | undefined;
|
||||
definition: string | null | undefined;
|
||||
status: string | undefined;
|
||||
status: ContentStatus | undefined;
|
||||
}[];
|
||||
index: number;
|
||||
categories: string[];
|
||||
|
@ -31,7 +31,7 @@ interface Props {
|
|||
|
||||
const DefinitionCard = ({ source, translations = [], index, categories }: Props): JSX.Element => {
|
||||
const isContentPanelAtLeastMd = useAtomGetter(atoms.containerQueries.isContentPanelAtLeastMd);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format, formatStatusDescription } = useFormat();
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback((item: Props["translations"][number]) => item.language, []),
|
||||
|
@ -40,7 +40,7 @@ const DefinitionCard = ({ source, translations = [], index, categories }: Props)
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap place-items-center gap-2">
|
||||
<p className="font-headers text-lg font-bold">{`${langui.definition} ${index}`}</p>
|
||||
<p className="font-headers text-lg font-bold">{format("definition_x", { x: index })}</p>
|
||||
|
||||
{translations.length > 1 && (
|
||||
<>
|
||||
|
@ -53,7 +53,7 @@ const DefinitionCard = ({ source, translations = [], index, categories }: Props)
|
|||
<>
|
||||
<Separator />
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||
content={formatStatusDescription(selectedTranslation.status)}
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.status} />
|
||||
</ToolTip>
|
||||
|
@ -80,7 +80,7 @@ const DefinitionCard = ({ source, translations = [], index, categories }: Props)
|
|||
"mt-3 flex place-items-center gap-2",
|
||||
cIf(!isContentPanelAtLeastMd, "flex-col text-center")
|
||||
)}>
|
||||
<p>{langui.source}: </p>
|
||||
<p>{format("source")}: </p>
|
||||
<Button href={source.url} size="small" text={source.name} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -21,11 +21,13 @@ import { LocalDataFile } from "graphql/fetchLocalData";
|
|||
const languages = atomPairing(atom<Languages>([]));
|
||||
const currencies = atomPairing(atom<Currencies>([]));
|
||||
const langui = atomPairing(atom<Langui>({}));
|
||||
const fallbackLangui = atomPairing(atom<Langui>({}));
|
||||
|
||||
export const localData = {
|
||||
languages: languages[0],
|
||||
currencies: currencies[0],
|
||||
langui: langui[0],
|
||||
fallbackLangui: fallbackLangui[0],
|
||||
};
|
||||
|
||||
const getFileName = (name: LocalDataFile): string => `/local-data/${name}.json`;
|
||||
|
@ -34,6 +36,7 @@ export const useLocalData = (): void => {
|
|||
const setLanguages = useAtomSetter(languages);
|
||||
const setCurrencies = useAtomSetter(currencies);
|
||||
const setLangui = useAtomSetter(langui);
|
||||
const setFallbackLangui = useAtomSetter(fallbackLangui);
|
||||
|
||||
const { locale } = useRouter();
|
||||
const { data: rawLanguages } = useFetch<LocalDataGetLanguagesQuery>(getFileName("languages"));
|
||||
|
@ -56,4 +59,9 @@ export const useLocalData = (): void => {
|
|||
console.log("[useLocalData] Refresh langui");
|
||||
setLangui(processLangui(rawLangui, locale));
|
||||
}, [locale, rawLangui, setLangui]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("[useLocalData] Refresh fallback langui");
|
||||
setFallbackLangui(processLangui(rawLangui, "en"));
|
||||
}, [rawLangui, setFallbackLangui]);
|
||||
};
|
||||
|
|
|
@ -41,7 +41,7 @@ export const getPostStaticProps =
|
|||
[langui.release_date ?? "Release date"]: [
|
||||
prettyDate(post.posts.data[0].attributes.date, context.locale),
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[langui.category ?? "Categories"]: filterHasAttributes(
|
||||
post.posts.data[0].attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
export interface ICUParams {
|
||||
library: never;
|
||||
contents: never;
|
||||
wiki: never;
|
||||
chronicles: never;
|
||||
library_short_description: never;
|
||||
contents_short_description: never;
|
||||
wiki_short_description: never;
|
||||
chronicles_short_description: never;
|
||||
news: never;
|
||||
gallery: never;
|
||||
archives: never;
|
||||
about_us: never;
|
||||
licensing_notice: never;
|
||||
copyright_notice: never;
|
||||
contents_description: never;
|
||||
type: { count: number };
|
||||
category: { count: number };
|
||||
size: never;
|
||||
release_date: never;
|
||||
release_year: never;
|
||||
details: never;
|
||||
price: never;
|
||||
width: never;
|
||||
height: never;
|
||||
thickness: never;
|
||||
subitem: { count: number };
|
||||
variant: { count: number };
|
||||
summary: never;
|
||||
audio: never;
|
||||
video: never;
|
||||
textual: never;
|
||||
game: never;
|
||||
other: never;
|
||||
left_to_right: never;
|
||||
right_to_left: never;
|
||||
page: { count: number };
|
||||
page_order: never;
|
||||
binding: never;
|
||||
type_information: never;
|
||||
front_matter: never;
|
||||
back_matter: never;
|
||||
open_content: never;
|
||||
read_content: never;
|
||||
watch_content: never;
|
||||
listen_content: never;
|
||||
view_scans: never;
|
||||
paperback: never;
|
||||
hardcover: never;
|
||||
select_language: never;
|
||||
language: { count: number };
|
||||
library_description: never;
|
||||
wiki_description: never;
|
||||
chronicles_description: never;
|
||||
news_description: never;
|
||||
archives_description: never;
|
||||
about_us_description: never;
|
||||
page_not_found: never;
|
||||
default_description: never;
|
||||
name: never;
|
||||
show_subitems: never;
|
||||
show_primary_items: never;
|
||||
show_secondary_items: never;
|
||||
order_by: never;
|
||||
group_by: never;
|
||||
select_option_sidebar: never;
|
||||
group: never;
|
||||
settings: never;
|
||||
theme: never;
|
||||
light: never;
|
||||
auto: never;
|
||||
dark: never;
|
||||
font_size: never;
|
||||
player_name: never;
|
||||
currency: never;
|
||||
font: never;
|
||||
calculated: never;
|
||||
status_incomplete: never;
|
||||
status_draft: never;
|
||||
status_review: never;
|
||||
status_done: never;
|
||||
incomplete: never;
|
||||
draft: never;
|
||||
review: never;
|
||||
done: never;
|
||||
status: never;
|
||||
transcribers: never;
|
||||
translators: never;
|
||||
proofreaders: never;
|
||||
transcript_notice: never;
|
||||
translation_notice: never;
|
||||
source_language: never;
|
||||
pronouns: never;
|
||||
item: { count: number };
|
||||
content: never;
|
||||
open_settings: never;
|
||||
change_language: never;
|
||||
open_search: never;
|
||||
chronology: never;
|
||||
accords_handbook: never;
|
||||
legality: never;
|
||||
sharing_policy: never;
|
||||
contact_us: never;
|
||||
email: never;
|
||||
email_gdpr_notice: never;
|
||||
message: never;
|
||||
send: never;
|
||||
response_invalid_code: never;
|
||||
response_invalid_email: never;
|
||||
response_email_success: never;
|
||||
always_show_info: never;
|
||||
item_not_available: never;
|
||||
primary_language: never;
|
||||
secondary_language: never;
|
||||
combine_related_contents: never;
|
||||
previous_content: never;
|
||||
followup_content: never;
|
||||
videos: never;
|
||||
view_on: never;
|
||||
channel: never;
|
||||
subscribers: never;
|
||||
description: never;
|
||||
available_at: never;
|
||||
search_title: never;
|
||||
want_it: never;
|
||||
have_it: never;
|
||||
source: never;
|
||||
reset_all_filters: never;
|
||||
only_display_items_i_have: never;
|
||||
only_display_items_i_want: never;
|
||||
only_display_unmarked_items: never;
|
||||
display_all_items: never;
|
||||
table_of_contents: never;
|
||||
no_results_message: never;
|
||||
all: never;
|
||||
special_pages: never;
|
||||
scan: never;
|
||||
scanlation: never;
|
||||
scanners: never;
|
||||
cleaners: never;
|
||||
typesetters: never;
|
||||
notes: never;
|
||||
cover: never;
|
||||
tags: never;
|
||||
no_source_warning: never;
|
||||
copy_anchor_link: never;
|
||||
anchor_link_copied: never;
|
||||
folders: never;
|
||||
empty_folder_message: never;
|
||||
switch_to_grid_view: never;
|
||||
switch_to_folder_view: never;
|
||||
content_is_not_available: never;
|
||||
paper_texture: never;
|
||||
book_fold: never;
|
||||
lighting: never;
|
||||
side_pages: never;
|
||||
shadow: never;
|
||||
night_reader: never;
|
||||
single_page_view: never;
|
||||
double_page_view: never;
|
||||
reset_all_options: never;
|
||||
reading_layout: never;
|
||||
quality: never;
|
||||
only_unavailable_videos: never;
|
||||
oldest: never;
|
||||
newest: never;
|
||||
least_popular: never;
|
||||
most_popular: never;
|
||||
shortest: never;
|
||||
longest: never;
|
||||
search: never;
|
||||
showing_x_out_of_y_results: {
|
||||
x: Date | boolean | number | string;
|
||||
y: Date | boolean | number | string;
|
||||
};
|
||||
return_to_x: { x: undefined | null | Date | boolean | number | string };
|
||||
x_results: { x: number };
|
||||
definition_x: { x: Date | boolean | number | string };
|
||||
subitem_of_x: { x: Date | boolean | number | string };
|
||||
variant_of_x: { x: Date | boolean | number | string };
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/* eslint-disable import/no-nodejs-modules */
|
||||
import { createWriteStream } from "fs";
|
||||
import { parse, TYPE } from "@formatjs/icu-messageformat-parser";
|
||||
import { getLangui } from "./fetchLocalData";
|
||||
import { filterDefined } from "helpers/asserts";
|
||||
|
||||
const OUTPUT_FOLDER = `${process.cwd()}/src/graphql`;
|
||||
|
||||
const icuToTypescript = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { ui_language, ...langui } = getLangui("en");
|
||||
|
||||
const output = createWriteStream(`${OUTPUT_FOLDER}/icuParams.ts`);
|
||||
|
||||
output.write("export interface ICUParams {\n");
|
||||
|
||||
Object.keys(langui).map((oKey) => {
|
||||
const key = oKey as keyof typeof langui;
|
||||
const parsedMessage = parse(langui[key] ?? "");
|
||||
|
||||
const variables = filterDefined(
|
||||
parsedMessage.map((elem) => {
|
||||
if (elem.type === TYPE.argument) {
|
||||
return `${elem.value}: Date | boolean | number | string`;
|
||||
} else if (elem.type === TYPE.plural) {
|
||||
return `${elem.value}: number`;
|
||||
} else if (elem.type === TYPE.select) {
|
||||
const options = Object.keys(elem.options);
|
||||
const stringOptions = options.filter(
|
||||
(option) => option !== "undefined" && option !== "other"
|
||||
);
|
||||
const type: string[] = stringOptions.map((option) => `"${option}"`);
|
||||
if (options.includes("undefined")) type.push(...["undefined", "null"]);
|
||||
if (options.includes("other")) type.push(...["Date", "boolean", "number", "string"]);
|
||||
return `${elem.value}: ${type.join(` | `)}`;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
|
||||
const variablesType = variables.length > 0 ? `{ ${variables.join(";")} }` : "never";
|
||||
|
||||
output.write(` ${key}: ${variablesType};\n`);
|
||||
});
|
||||
|
||||
output.write("}\n");
|
||||
|
||||
console.log(`${OUTPUT_FOLDER}/icu-params.ts has been written!`);
|
||||
};
|
||||
|
||||
if (process.argv[2] === "--icu") {
|
||||
icuToTypescript();
|
||||
}
|
|
@ -18,7 +18,6 @@ query localDataGetWebsiteInterfaces {
|
|||
wiki_short_description
|
||||
chronicles_short_description
|
||||
news
|
||||
merch
|
||||
gallery
|
||||
archives
|
||||
about_us
|
||||
|
@ -27,7 +26,6 @@ query localDataGetWebsiteInterfaces {
|
|||
contents_description
|
||||
type
|
||||
category
|
||||
categories
|
||||
size
|
||||
release_date
|
||||
release_year
|
||||
|
@ -37,22 +35,16 @@ query localDataGetWebsiteInterfaces {
|
|||
height
|
||||
thickness
|
||||
subitem
|
||||
subitems
|
||||
subitem_of
|
||||
variant
|
||||
variants
|
||||
variant_of
|
||||
summary
|
||||
audio
|
||||
video
|
||||
textual
|
||||
game
|
||||
other
|
||||
return_to
|
||||
left_to_right
|
||||
right_to_left
|
||||
page
|
||||
pages
|
||||
page_order
|
||||
binding
|
||||
type_information
|
||||
|
@ -65,15 +57,12 @@ query localDataGetWebsiteInterfaces {
|
|||
view_scans
|
||||
paperback
|
||||
hardcover
|
||||
languages
|
||||
select_language
|
||||
language
|
||||
library_description
|
||||
wiki_description
|
||||
chronicles_description
|
||||
news_description
|
||||
merch_description
|
||||
gallery_description
|
||||
archives_description
|
||||
about_us_description
|
||||
page_not_found
|
||||
|
@ -82,8 +71,6 @@ query localDataGetWebsiteInterfaces {
|
|||
show_subitems
|
||||
show_primary_items
|
||||
show_secondary_items
|
||||
no_type
|
||||
no_year
|
||||
order_by
|
||||
group_by
|
||||
select_option_sidebar
|
||||
|
@ -114,20 +101,14 @@ query localDataGetWebsiteInterfaces {
|
|||
translation_notice
|
||||
source_language
|
||||
pronouns
|
||||
no_category
|
||||
item
|
||||
items
|
||||
content
|
||||
result
|
||||
results
|
||||
language_switch_message
|
||||
open_settings
|
||||
change_language
|
||||
open_search
|
||||
chronology
|
||||
accords_handbook
|
||||
legality
|
||||
members
|
||||
sharing_policy
|
||||
contact_us
|
||||
email
|
||||
|
@ -160,7 +141,6 @@ query localDataGetWebsiteInterfaces {
|
|||
only_display_unmarked_items
|
||||
display_all_items
|
||||
table_of_contents
|
||||
definition
|
||||
no_results_message
|
||||
all
|
||||
special_pages
|
||||
|
@ -199,6 +179,12 @@ query localDataGetWebsiteInterfaces {
|
|||
shortest
|
||||
longest
|
||||
search
|
||||
showing_x_out_of_y_results
|
||||
return_to_x
|
||||
x_results
|
||||
definition_x
|
||||
subitem_of_x
|
||||
variant_of_x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { convertPrice } from "./numbers";
|
||||
import { isDefinedAndNotEmpty, isUndefined } from "./asserts";
|
||||
import { datePickerToDate } from "./date";
|
||||
import { Currencies, Languages, Langui } from "./localData";
|
||||
import { Currencies, Languages } from "./localData";
|
||||
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||
|
||||
export const prettyDate = (
|
||||
|
@ -58,25 +58,6 @@ export const prettyInlineTitle = (
|
|||
return result;
|
||||
};
|
||||
|
||||
export const prettyItemType = (metadata: { __typename: string }, langui: Langui): string => {
|
||||
switch (metadata.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
return langui.audio ?? "Audio";
|
||||
case "ComponentMetadataBooks":
|
||||
return langui.textual ?? "Textual";
|
||||
case "ComponentMetadataGame":
|
||||
return langui.game ?? "Game";
|
||||
case "ComponentMetadataVideo":
|
||||
return langui.video ?? "Video";
|
||||
case "ComponentMetadataGroup":
|
||||
return langui.group ?? "Group";
|
||||
case "ComponentMetadataOther":
|
||||
return langui.other ?? "Other";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/* eslint-disable id-denylist */
|
||||
export const prettyItemSubType = (
|
||||
metadata:
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { Langui } from "./localData";
|
||||
import { isDefined } from "./asserts";
|
||||
import {
|
||||
Enum_Componentsetstextset_Status,
|
||||
GetLibraryItemQuery,
|
||||
GetLibraryItemScansQuery,
|
||||
} from "graphql/generated";
|
||||
import { GetLibraryItemQuery, GetLibraryItemScansQuery } from "graphql/generated";
|
||||
|
||||
type SortRangedContentProps =
|
||||
| NonNullable<
|
||||
|
@ -26,25 +21,6 @@ export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
|||
});
|
||||
};
|
||||
|
||||
export const getStatusDescription = (status: string, langui: Langui): string | null | undefined => {
|
||||
switch (status) {
|
||||
case Enum_Componentsetstextset_Status.Incomplete:
|
||||
return langui.status_incomplete;
|
||||
|
||||
case Enum_Componentsetstextset_Status.Draft:
|
||||
return langui.status_draft;
|
||||
|
||||
case Enum_Componentsetstextset_Status.Review:
|
||||
return langui.status_review;
|
||||
|
||||
case Enum_Componentsetstextset_Status.Done:
|
||||
return langui.status_done;
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const iterateMap = <K, V, U>(
|
||||
map: Map<K, V>,
|
||||
callbackfn: (key: K, value: V, index: number) => U,
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import { IntlMessageFormat } from "intl-messageformat";
|
||||
import { useCallback } from "react";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { LibraryItemMetadataDynamicZone } from "graphql/generated";
|
||||
import { ICUParams } from "graphql/icuParams";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
|
||||
type WordingKey = keyof ICUParams;
|
||||
|
||||
type LibraryItemType = Exclude<LibraryItemMetadataDynamicZone["__typename"], undefined>;
|
||||
|
||||
export type ContentStatus = "Done" | "Draft" | "Incomplete" | "Review";
|
||||
|
||||
const componentMetadataToWording: Record<LibraryItemType, WordingKey> = {
|
||||
ComponentMetadataAudio: "audio",
|
||||
ComponentMetadataBooks: "textual",
|
||||
ComponentMetadataGame: "game",
|
||||
ComponentMetadataGroup: "group",
|
||||
ComponentMetadataVideo: "video",
|
||||
ComponentMetadataOther: "other",
|
||||
Error: "item",
|
||||
};
|
||||
|
||||
const componentSetsTextsetStatusToWording: Record<
|
||||
ContentStatus,
|
||||
{ label: WordingKey; description: WordingKey }
|
||||
> = {
|
||||
Draft: { label: "draft", description: "status_draft" },
|
||||
Incomplete: { label: "incomplete", description: "status_incomplete" },
|
||||
Review: { label: "review", description: "status_review" },
|
||||
Done: { label: "done", description: "status_done" },
|
||||
};
|
||||
|
||||
export const useFormat = (): {
|
||||
format: <K extends WordingKey>(
|
||||
key: K,
|
||||
...values: ICUParams[K] extends never ? [undefined?] : [ICUParams[K]]
|
||||
) => string;
|
||||
formatLibraryItemType: (metadata: { __typename: LibraryItemType }) => string;
|
||||
formatStatusLabel: (status: ContentStatus) => string;
|
||||
formatStatusDescription: (status: ContentStatus) => string;
|
||||
} => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const fallbackLangui = useAtomGetter(atoms.localData.fallbackLangui);
|
||||
|
||||
const format = useCallback(
|
||||
(
|
||||
key: WordingKey,
|
||||
values?: Record<string, Date | boolean | number | string | null | undefined>
|
||||
): string => {
|
||||
const processedValues = Object.fromEntries(
|
||||
Object.entries(values ?? {}).map(([oKey, value]) => [
|
||||
oKey,
|
||||
isDefined(value) ? value : "undefined",
|
||||
])
|
||||
);
|
||||
const result = new IntlMessageFormat(langui[key] ?? "").format(processedValues).toString();
|
||||
if (isDefinedAndNotEmpty(result)) {
|
||||
return result;
|
||||
}
|
||||
return new IntlMessageFormat(fallbackLangui[key] ?? "").format(processedValues).toString();
|
||||
},
|
||||
[langui, fallbackLangui]
|
||||
);
|
||||
|
||||
const formatLibraryItemType = useCallback(
|
||||
(metadata: { __typename: LibraryItemType }): string =>
|
||||
format(componentMetadataToWording[metadata.__typename]),
|
||||
[format]
|
||||
);
|
||||
|
||||
const formatStatusLabel = useCallback(
|
||||
(status: ContentStatus): string => format(componentSetsTextsetStatusToWording[status].label),
|
||||
[format]
|
||||
);
|
||||
|
||||
const formatStatusDescription = useCallback(
|
||||
(status: ContentStatus): string =>
|
||||
format(componentSetsTextsetStatusToWording[status].description),
|
||||
[format]
|
||||
);
|
||||
|
||||
return {
|
||||
format,
|
||||
formatLibraryItemType,
|
||||
formatStatusLabel,
|
||||
formatStatusDescription,
|
||||
};
|
||||
};
|
|
@ -5,8 +5,7 @@ import { ContentPanel } from "components/Containers/ContentPanel";
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { Img } from "components/Img";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -16,7 +15,7 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const FourOhFour = ({ openGraph, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<AppLayout
|
||||
contentPanel={
|
||||
|
@ -26,7 +25,7 @@ const FourOhFour = ({ openGraph, ...otherProps }: Props): JSX.Element => {
|
|||
className="animate-zoom-in drop-shadow-lg shadow-shade"
|
||||
/>
|
||||
<div className="mt-8 grid place-items-center gap-6">
|
||||
<h2>{langui.page_not_found}</h2>
|
||||
<h2>{format("page_not_found")}</h2>
|
||||
<ReturnButton href="/" title="Home" />
|
||||
</div>
|
||||
</ContentPanel>
|
||||
|
|
|
@ -5,8 +5,7 @@ import { ContentPanel } from "components/Containers/ContentPanel";
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { Img } from "components/Img";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -16,7 +15,7 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const FiveHundred = ({ openGraph, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<AppLayout
|
||||
contentPanel={
|
||||
|
@ -26,7 +25,7 @@ const FiveHundred = ({ openGraph, ...otherProps }: Props): JSX.Element => {
|
|||
className="animate-zoom-in drop-shadow-lg shadow-shade"
|
||||
/>
|
||||
<div className="mt-8 grid place-items-center gap-6">
|
||||
<h2>{langui.page_not_found}</h2>
|
||||
<h2>{format("page_not_found")}</h2>
|
||||
<ReturnButton href="/" title="Home" />
|
||||
</div>
|
||||
</ContentPanel>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -9,12 +8,12 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
*/
|
||||
|
||||
const AccordsHandbook = (props: PostStaticProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<PostPage
|
||||
{...props}
|
||||
returnHref="/about-us/"
|
||||
returnTitle={langui.about_us}
|
||||
returnTitle={format("about_us")}
|
||||
displayToc
|
||||
displayLanguageSwitcher
|
||||
/>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
|||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -17,7 +18,7 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
|
||||
const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||
const router = useRouter();
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
const [formResponse, setFormResponse] = useState("");
|
||||
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">("stale");
|
||||
|
@ -65,13 +66,13 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
.then((response: ResponseMailProps) => {
|
||||
switch (response.code) {
|
||||
case "OKAY":
|
||||
setFormResponse(langui.response_email_success ?? "");
|
||||
setFormResponse(format("response_email_success"));
|
||||
setFormState("completed");
|
||||
sendAnalytics("Contact", "Send email (success)");
|
||||
break;
|
||||
|
||||
case "EENVELOPE":
|
||||
setFormResponse(langui.response_invalid_email ?? "");
|
||||
setFormResponse(format("response_invalid_email"));
|
||||
setFormState("stale");
|
||||
sendAnalytics("Contact", "Send email (invalid email)");
|
||||
break;
|
||||
|
@ -84,7 +85,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
setFormResponse(langui.response_invalid_code ?? "");
|
||||
setFormResponse(format("response_invalid_code"));
|
||||
setFormState("stale");
|
||||
setRandomNumber1(randomInt(0, 10));
|
||||
setRandomNumber2(randomInt(0, 10));
|
||||
|
@ -94,7 +95,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
fields.verif.value = "";
|
||||
}}>
|
||||
<div className="flex flex-col place-items-center gap-1">
|
||||
<label htmlFor="name">{langui.name}:</label>
|
||||
<label htmlFor="name">{format("name")}:</label>
|
||||
<input
|
||||
type="text"
|
||||
className={cIf(is1ColumnLayout, "w-full")}
|
||||
|
@ -106,7 +107,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
</div>
|
||||
|
||||
<div className="flex flex-col place-items-center gap-1">
|
||||
<label htmlFor="email">{langui.email}:</label>
|
||||
<label htmlFor="email">{format("email")}:</label>
|
||||
<input
|
||||
type="email"
|
||||
className={cIf(is1ColumnLayout, "w-full")}
|
||||
|
@ -115,11 +116,11 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
required
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
<p className="text-sm italic text-dark opacity-70">{langui.email_gdpr_notice}</p>
|
||||
<p className="text-sm italic text-dark opacity-70">{format("email_gdpr_notice")}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col place-items-center gap-1">
|
||||
<label htmlFor="message">{langui.message}:</label>
|
||||
<label htmlFor="message">{format("message")}:</label>
|
||||
<textarea
|
||||
name="message"
|
||||
id="message"
|
||||
|
@ -147,7 +148,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
|
||||
<input
|
||||
type="submit"
|
||||
value={langui.send ?? "Send"}
|
||||
value={format("send")}
|
||||
className="w-min !px-6"
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
|
@ -168,7 +169,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
<PostPage
|
||||
{...props}
|
||||
returnHref="/about-us/"
|
||||
returnTitle={langui.about_us}
|
||||
returnTitle={format("about_us")}
|
||||
displayToc
|
||||
appendBody={contactForm}
|
||||
displayLanguageSwitcher
|
||||
|
|
|
@ -6,8 +6,7 @@ import { SubPanel } from "components/Containers/SubPanel";
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -17,23 +16,23 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const AboutUs = (props: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="info"
|
||||
title={langui.about_us}
|
||||
description={langui.about_us_description}
|
||||
title={format("about_us")}
|
||||
description={format("about_us_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<NavOption title={langui.accords_handbook} url="/about-us/accords-handbook" border />
|
||||
<NavOption title={langui.legality} url="/about-us/legality" border />
|
||||
<NavOption title={langui.sharing_policy} url="/about-us/sharing-policy" border />
|
||||
<NavOption title={langui.contact_us} url="/about-us/contact" border />
|
||||
<NavOption title={format("accords_handbook")} url="/about-us/accords-handbook" border />
|
||||
<NavOption title={format("legality")} url="/about-us/legality" border />
|
||||
<NavOption title={format("sharing_policy")} url="/about-us/sharing-policy" border />
|
||||
<NavOption title={format("contact_us")} url="/about-us/contact" border />
|
||||
</SubPanel>
|
||||
}
|
||||
{...props}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -9,12 +8,12 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
*/
|
||||
|
||||
const Legality = (props: PostStaticProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<PostPage
|
||||
{...props}
|
||||
returnHref="/about-us/"
|
||||
returnTitle={langui.about_us}
|
||||
returnTitle={format("about_us")}
|
||||
displayToc
|
||||
displayLanguageSwitcher
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -9,12 +8,12 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
*/
|
||||
|
||||
const SharingPolicy = (props: PostStaticProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<PostPage
|
||||
{...props}
|
||||
returnHref="/about-us/"
|
||||
returnTitle={langui.about_us}
|
||||
returnTitle={format("about_us")}
|
||||
displayToc
|
||||
displayLanguageSwitcher
|
||||
/>
|
||||
|
|
|
@ -6,8 +6,7 @@ import { SubPanel } from "components/Containers/SubPanel";
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -17,10 +16,14 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const Archives = (props: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader icon="save" title={langui.archives} description={langui.archives_description} />
|
||||
<PanelHeader
|
||||
icon="save"
|
||||
title={format("archives")}
|
||||
description={format("archives_description")}
|
||||
/>
|
||||
<HorizontalLine />
|
||||
<NavOption title={"Videos"} url="/archives/videos/" border />
|
||||
</SubPanel>
|
||||
|
|
|
@ -14,8 +14,6 @@ import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { MeiliIndices, MeiliVideo } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
|
@ -28,6 +26,7 @@ import { Button } from "components/Inputs/Button";
|
|||
import { GetVideoChannelQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -61,27 +60,20 @@ interface Props extends AppLayoutRequired {
|
|||
}
|
||||
|
||||
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
|
||||
const sortingMethods = useMemo(
|
||||
() => [
|
||||
{ meiliAttribute: "sortable_published_date:asc", displayedName: langui.oldest },
|
||||
{ meiliAttribute: "sortable_published_date:desc", displayedName: langui.newest },
|
||||
{ meiliAttribute: "views:asc", displayedName: langui.least_popular },
|
||||
{ meiliAttribute: "views:desc", displayedName: langui.most_popular },
|
||||
{ meiliAttribute: "duration:asc", displayedName: langui.shortest },
|
||||
{ meiliAttribute: "duration:desc", displayedName: langui.longest },
|
||||
{ meiliAttribute: "sortable_published_date:asc", displayedName: format("oldest") },
|
||||
{ meiliAttribute: "sortable_published_date:desc", displayedName: format("newest") },
|
||||
{ meiliAttribute: "views:asc", displayedName: format("least_popular") },
|
||||
{ meiliAttribute: "views:desc", displayedName: format("most_popular") },
|
||||
{ meiliAttribute: "duration:asc", displayedName: format("shortest") },
|
||||
{ meiliAttribute: "duration:desc", displayedName: format("longest") },
|
||||
],
|
||||
[
|
||||
langui.least_popular,
|
||||
langui.longest,
|
||||
langui.most_popular,
|
||||
langui.newest,
|
||||
langui.oldest,
|
||||
langui.shortest,
|
||||
]
|
||||
[format]
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -159,7 +151,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos"
|
||||
title={langui.videos}
|
||||
title={format("videos")}
|
||||
displayOnlyOn={"3ColumnsLayout"}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
@ -167,14 +159,16 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
<PanelHeader
|
||||
icon="movie"
|
||||
title={channel.title}
|
||||
description={`${channel.subscribers.toLocaleString()} ${langui.subscribers?.toLowerCase()}`}
|
||||
description={`${channel.subscribers.toLocaleString()} ${format(
|
||||
"subscribers"
|
||||
).toLowerCase()}`}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title}
|
||||
placeholder={format("search_title")}
|
||||
value={query}
|
||||
onChange={(newQuery) => {
|
||||
setPage(1);
|
||||
|
@ -187,10 +181,10 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={langui.order_by}>
|
||||
<WithLabel label={format("order_by")}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={sortingMethods.map((item) => item.displayedName ?? "")}
|
||||
options={sortingMethods.map((item) => item.displayedName)}
|
||||
value={sortingMethod}
|
||||
onChange={(newSort) => {
|
||||
setPage(1);
|
||||
|
@ -203,7 +197,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.only_unavailable_videos}>
|
||||
<WithLabel label={format("only_unavailable_videos")}>
|
||||
<Switch
|
||||
value={onlyShowGone}
|
||||
onClick={() => {
|
||||
|
@ -213,14 +207,14 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setOnlyShowGone(DEFAULT_FILTERS_STATE.onlyShowGone);
|
||||
|
|
|
@ -14,8 +14,6 @@ import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { MeiliIndices, MeiliVideo } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
|
@ -26,6 +24,7 @@ import { Select } from "components/Inputs/Select";
|
|||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -55,27 +54,20 @@ const queryParamSchema = z.object({
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
|
||||
const sortingMethods = useMemo(
|
||||
() => [
|
||||
{ meiliAttribute: "sortable_published_date:asc", displayedName: langui.oldest },
|
||||
{ meiliAttribute: "sortable_published_date:desc", displayedName: langui.newest },
|
||||
{ meiliAttribute: "views:asc", displayedName: langui.least_popular },
|
||||
{ meiliAttribute: "views:desc", displayedName: langui.most_popular },
|
||||
{ meiliAttribute: "duration:asc", displayedName: langui.shortest },
|
||||
{ meiliAttribute: "duration:desc", displayedName: langui.longest },
|
||||
{ meiliAttribute: "sortable_published_date:asc", displayedName: format("oldest") },
|
||||
{ meiliAttribute: "sortable_published_date:desc", displayedName: format("newest") },
|
||||
{ meiliAttribute: "views:asc", displayedName: format("least_popular") },
|
||||
{ meiliAttribute: "views:desc", displayedName: format("most_popular") },
|
||||
{ meiliAttribute: "duration:asc", displayedName: format("shortest") },
|
||||
{ meiliAttribute: "duration:desc", displayedName: format("longest") },
|
||||
],
|
||||
[
|
||||
langui.least_popular,
|
||||
langui.longest,
|
||||
langui.most_popular,
|
||||
langui.newest,
|
||||
langui.oldest,
|
||||
langui.shortest,
|
||||
]
|
||||
[format]
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -158,13 +150,17 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
|||
className="mb-10"
|
||||
/>
|
||||
|
||||
<PanelHeader icon="movie" title={langui.videos} description={langui.archives_description} />
|
||||
<PanelHeader
|
||||
icon="movie"
|
||||
title={format("videos")}
|
||||
description={format("archives_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title}
|
||||
placeholder={format("search_title")}
|
||||
value={query}
|
||||
onChange={(newQuery) => {
|
||||
setPage(1);
|
||||
|
@ -177,10 +173,10 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={langui.order_by}>
|
||||
<WithLabel label={format("order_by")}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={sortingMethods.map((item) => item.displayedName ?? "")}
|
||||
options={sortingMethods.map((item) => item.displayedName)}
|
||||
value={sortingMethod}
|
||||
onChange={(newSort) => {
|
||||
setPage(1);
|
||||
|
@ -193,7 +189,7 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.only_unavailable_videos}>
|
||||
<WithLabel label={format("only_unavailable_videos")}>
|
||||
<Switch
|
||||
value={onlyShowGone}
|
||||
onClick={() => {
|
||||
|
@ -203,14 +199,14 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
|
|||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch value={keepInfoVisible} onClick={toggleKeepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
|
|
|
@ -19,6 +19,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -31,22 +32,22 @@ interface Props extends AppLayoutRequired {
|
|||
|
||||
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const router = useRouter();
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos/"
|
||||
title={langui.videos}
|
||||
title={format("videos")}
|
||||
displayOnlyOn={"3ColumnsLayout"}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<NavOption title={langui.video} url="#video" border />
|
||||
<NavOption title={langui.channel} url="#channel" border />
|
||||
<NavOption title={langui.description} url="#description" border />
|
||||
<NavOption title={format("video")} url="#video" border />
|
||||
<NavOption title={format("channel")} url="#channel" border />
|
||||
<NavOption title={format("description")} url="#description" border />
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
|
@ -54,7 +55,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
title={format("library")}
|
||||
displayOnlyOn={"1ColumnLayout"}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
@ -97,7 +98,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
</p>
|
||||
)}
|
||||
<Link href={`https://youtu.be/${video.uid}`} alwaysNewTab>
|
||||
<Button size="small" text={`${langui.view_on} ${video.source}`} />
|
||||
<Button size="small" text={`${format("view_on")} ${video.source}`} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -106,7 +107,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
{video.channel?.data?.attributes && (
|
||||
<InsetBox id="channel" className="grid place-items-center">
|
||||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-4 text-center">
|
||||
<h2 className="text-2xl">{langui.channel}</h2>
|
||||
<h2 className="text-2xl">{format("channel")}</h2>
|
||||
<div>
|
||||
<Button
|
||||
href={`/archives/videos/c/${video.channel.data.attributes.uid}\
|
||||
|
@ -115,7 +116,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
<p>
|
||||
{`${video.channel.data.attributes.subscribers.toLocaleString()}
|
||||
${langui.subscribers?.toLowerCase()}`}
|
||||
${format("subscribers").toLowerCase()}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -124,7 +125,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
<InsetBox id="description" className="grid place-items-center">
|
||||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
||||
<h2 className="text-2xl">{langui.description}</h2>
|
||||
<h2 className="text-2xl">{format("description")}</h2>
|
||||
<p className="whitespace-pre-line">{video.description}</p>
|
||||
</div>
|
||||
</InsetBox>
|
||||
|
|
|
@ -18,10 +18,9 @@ import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/local
|
|||
import { getDescription } from "helpers/description";
|
||||
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { Ids } from "types/ids";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -34,7 +33,7 @@ interface Props extends AppLayoutRequired {
|
|||
}
|
||||
|
||||
const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
useScrollTopOnChange(Ids.ContentPanel, [chronicle.slug]);
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
|
@ -71,7 +70,7 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
|
|||
<ReturnButton
|
||||
displayOnlyOn={"1ColumnLayout"}
|
||||
href="/chronicles"
|
||||
title={langui.chronicles}
|
||||
title={format("chronicles")}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
|
@ -119,7 +118,11 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
|
|||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton displayOnlyOn={"3ColumnsLayout"} href="/chronicles" title={langui.chronicles} />
|
||||
<ReturnButton
|
||||
displayOnlyOn={"3ColumnsLayout"}
|
||||
href="/chronicles"
|
||||
title={format("chronicles")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -194,7 +197,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.type?.data
|
||||
?.attributes?.titles?.[0]?.title,
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[langui.category ?? "Categories"]: filterHasAttributes(
|
||||
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.categories
|
||||
?.data,
|
||||
["attributes"] as const
|
||||
|
|
|
@ -10,8 +10,7 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -23,13 +22,13 @@ interface Props extends AppLayoutRequired {
|
|||
}
|
||||
|
||||
const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="schedule"
|
||||
title={langui.chronicles}
|
||||
description={langui.chronicles_description}
|
||||
title={format("chronicles")}
|
||||
description={format("chronicles_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
prettySlug,
|
||||
} from "helpers/formatters";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { filterHasAttributes, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { ContentWithTranslations } from "types/types";
|
||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
|
@ -35,6 +34,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
import { Ids } from "types/ids";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -49,7 +49,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
const isContentPanelAtLeast2xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast2xl);
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format, formatStatusDescription } = useFormat();
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
|
@ -86,9 +86,8 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
fallback: {
|
||||
title: content.folder?.data?.attributes
|
||||
? prettySlug(content.folder.data.attributes.slug)
|
||||
: langui.contents,
|
||||
: format("contents"),
|
||||
},
|
||||
langui,
|
||||
};
|
||||
|
||||
const subPanel = (
|
||||
|
@ -102,14 +101,14 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
<h2 className="text-xl">
|
||||
{selectedTranslation.text_set.source_language.data.attributes.code ===
|
||||
selectedTranslation.language?.data?.attributes?.code
|
||||
? langui.transcript_notice
|
||||
: langui.translation_notice}
|
||||
? format("transcript_notice")
|
||||
: format("translation_notice")}
|
||||
</h2>
|
||||
|
||||
{selectedTranslation.text_set.source_language.data.attributes.code !==
|
||||
selectedTranslation.language?.data?.attributes?.code && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers font-bold">{langui.source_language}:</p>
|
||||
<p className="font-headers font-bold">{format("source_language")}:</p>
|
||||
<Chip
|
||||
text={prettyLanguage(
|
||||
selectedTranslation.text_set.source_language.data.attributes.code,
|
||||
|
@ -120,10 +119,10 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
)}
|
||||
|
||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<p className="font-headers font-bold">{format("status")}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTranslation.text_set.status, langui)}
|
||||
content={formatStatusDescription(selectedTranslation.text_set.status)}
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.text_set.status} />
|
||||
</ToolTip>
|
||||
|
@ -132,7 +131,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{selectedTranslation.text_set.transcribers &&
|
||||
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.transcribers}:</p>
|
||||
<p className="font-headers font-bold">{format("transcribers")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedTranslation.text_set.transcribers.data, [
|
||||
"attributes",
|
||||
|
@ -149,7 +148,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{selectedTranslation.text_set.translators &&
|
||||
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.translators}:</p>
|
||||
<p className="font-headers font-bold">{format("translators")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedTranslation.text_set.translators.data, [
|
||||
"attributes",
|
||||
|
@ -166,7 +165,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{selectedTranslation.text_set.proofreaders &&
|
||||
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.proofreaders}:</p>
|
||||
<p className="font-headers font-bold">{format("proofreaders")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedTranslation.text_set.proofreaders.data, [
|
||||
"attributes",
|
||||
|
@ -182,7 +181,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.notes}:</p>
|
||||
<p className="font-headers font-bold">{format("notes")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
<Markdawn text={selectedTranslation.text_set.notes} />
|
||||
</div>
|
||||
|
@ -210,7 +209,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
<>
|
||||
<HorizontalLine />
|
||||
<div>
|
||||
<p className="font-headers text-2xl font-bold">{langui.source}</p>
|
||||
<p className="font-headers text-2xl font-bold">{format("source")}</p>
|
||||
<div className="mt-6 grid place-items-center gap-6">
|
||||
{filterHasAttributes(content.ranged_contents.data, [
|
||||
"attributes.library_item.data.attributes",
|
||||
|
@ -283,7 +282,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{previousContent?.attributes && (
|
||||
<div className="mt-12 mb-8 w-full">
|
||||
<h2 className="mb-4 text-center text-2xl">{langui.previous_content}</h2>
|
||||
<h2 className="mb-4 text-center text-2xl">{format("previous_content")}</h2>
|
||||
<TranslatedPreviewLine
|
||||
href={`/contents/${previousContent.attributes.slug}`}
|
||||
translations={filterHasAttributes(previousContent.attributes.translations, [
|
||||
|
@ -328,7 +327,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{nextContent?.attributes && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<h2 className="mb-4 text-center text-2xl">{langui.followup_content}</h2>
|
||||
<h2 className="mb-4 text-center text-2xl">{format("followup_content")}</h2>
|
||||
<TranslatedPreviewLine
|
||||
href={`/contents/${nextContent.attributes.slug}`}
|
||||
translations={filterHasAttributes(nextContent.attributes.translations, [
|
||||
|
@ -404,7 +403,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
[langui.type ?? "Type"]: [
|
||||
content.contents.data[0].attributes.type?.data?.attributes?.titles?.[0]?.title,
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[langui.category ?? "Categories"]: filterHasAttributes(
|
||||
content.contents.data[0].attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
|
|
|
@ -22,14 +22,13 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { MeiliContent, MeiliIndices } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -58,16 +57,16 @@ interface Props extends AppLayoutRequired {}
|
|||
|
||||
const Contents = (props: Props): JSX.Element => {
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
|
||||
const sortingMethods = useMemo(
|
||||
() => [
|
||||
{ meiliAttribute: "slug:asc", displayedName: langui.name },
|
||||
{ meiliAttribute: "sortable_updated_date:asc", displayedName: langui.oldest },
|
||||
{ meiliAttribute: "sortable_updated_date:desc", displayedName: langui.newest },
|
||||
{ meiliAttribute: "slug:asc", displayedName: format("name") },
|
||||
{ meiliAttribute: "sortable_updated_date:asc", displayedName: format("oldest") },
|
||||
{ meiliAttribute: "sortable_updated_date:desc", displayedName: format("newest") },
|
||||
],
|
||||
[langui.name, langui.newest, langui.oldest]
|
||||
[format]
|
||||
);
|
||||
|
||||
const [sortingMethod, setSortingMethod] = useState<number>(
|
||||
|
@ -131,19 +130,19 @@ const Contents = (props: Props): JSX.Element => {
|
|||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
description={langui.contents_description}
|
||||
title={format("contents")}
|
||||
description={format("contents_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<Button href="/contents" text={langui.switch_to_folder_view} icon="folder" />
|
||||
<Button href="/contents" text={format("switch_to_folder_view")} icon="folder" />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title ?? "Search..."}
|
||||
placeholder={format("search_title")}
|
||||
value={query}
|
||||
onChange={(name) => {
|
||||
setPage(1);
|
||||
|
@ -156,10 +155,10 @@ const Contents = (props: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={langui.order_by}>
|
||||
<WithLabel label={format("order_by")}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={sortingMethods.map((item) => item.displayedName ?? "")}
|
||||
options={sortingMethods.map((item) => item.displayedName)}
|
||||
value={sortingMethod}
|
||||
onChange={(newSort) => {
|
||||
setPage(1);
|
||||
|
@ -173,7 +172,7 @@ const Contents = (props: Props): JSX.Element => {
|
|||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
|
@ -186,7 +185,7 @@ const Contents = (props: Props): JSX.Element => {
|
|||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
|
|
|
@ -20,6 +20,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -33,20 +34,20 @@ interface Props extends AppLayoutRequired {
|
|||
}
|
||||
|
||||
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="workspaces"
|
||||
title={langui.contents}
|
||||
description={langui.contents_description}
|
||||
title={format("contents")}
|
||||
description={format("contents_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<Button href="/contents/all" text={langui.switch_to_grid_view} icon="apps" />
|
||||
<Button href="/contents/all" text={format("switch_to_grid_view")} icon="apps" />
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
|
@ -117,7 +118,7 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
|
|||
)
|
||||
)}
|
||||
renderWhenEmpty={() => <></>}
|
||||
groupingFunction={() => [langui.folders ?? "Folders"]}
|
||||
groupingFunction={() => [format("folders")]}
|
||||
/>
|
||||
|
||||
<SmartList
|
||||
|
@ -159,7 +160,7 @@ const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Elemen
|
|||
"grid-cols-2 gap-x-3 gap-y-5"
|
||||
)}
|
||||
renderWhenEmpty={() => <></>}
|
||||
groupingFunction={() => [langui.contents ?? "Contents"]}
|
||||
groupingFunction={() => [format("contents")]}
|
||||
/>
|
||||
|
||||
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
|
||||
|
@ -258,13 +259,13 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
*/
|
||||
|
||||
const NoContentNorFolderMessage = () => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<div className="grid place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
<p className="max-w-xs text-2xl">{langui.empty_folder_message}</p>
|
||||
<p className="max-w-xs text-2xl">{format("empty_folder_message")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
prettyDate,
|
||||
prettyInlineTitle,
|
||||
prettyItemSubType,
|
||||
prettyItemType,
|
||||
prettyPrice,
|
||||
prettySlug,
|
||||
prettyURL,
|
||||
|
@ -54,6 +53,7 @@ import { Ids } from "types/ids";
|
|||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { Link } from "components/Inputs/Link";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -74,7 +74,7 @@ interface Props extends AppLayoutRequired {
|
|||
|
||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||
const currency = useAtomGetter(atoms.settings.currency);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format, formatLibraryItemType } = useFormat();
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
|
||||
const isContentPanelAtLeast3xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast3xl);
|
||||
|
@ -99,13 +99,13 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton href="/library/" title={langui.library} displayOnlyOn="3ColumnsLayout" />
|
||||
<ReturnButton href="/library/" title={format("library")} displayOnlyOn="3ColumnsLayout" />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<div className="grid gap-4">
|
||||
<NavOption
|
||||
title={langui.summary}
|
||||
title={format("summary")}
|
||||
url={`#${intersectionIds[0]}`}
|
||||
border
|
||||
active={currentIntersection === 0}
|
||||
|
@ -113,7 +113,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.gallery && item.gallery.data.length > 0 && (
|
||||
<NavOption
|
||||
title={langui.gallery}
|
||||
title={format("gallery")}
|
||||
url={`#${intersectionIds[1]}`}
|
||||
border
|
||||
active={currentIntersection === 1}
|
||||
|
@ -121,7 +121,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
)}
|
||||
|
||||
<NavOption
|
||||
title={langui.details}
|
||||
title={format("details")}
|
||||
url={`#${intersectionIds[2]}`}
|
||||
border
|
||||
active={currentIntersection === 2}
|
||||
|
@ -129,7 +129,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.subitems && item.subitems.data.length > 0 && (
|
||||
<NavOption
|
||||
title={isVariantSet ? langui.variants : langui.subitems}
|
||||
title={format(isVariantSet ? "variant" : "subitem", { count: Infinity })}
|
||||
url={`#${intersectionIds[3]}`}
|
||||
border
|
||||
active={currentIntersection === 3}
|
||||
|
@ -138,7 +138,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.contents && item.contents.data.length > 0 && (
|
||||
<NavOption
|
||||
title={langui.contents}
|
||||
title={format("contents")}
|
||||
url={`#${intersectionIds[4]}`}
|
||||
border
|
||||
active={currentIntersection === 4}
|
||||
|
@ -152,7 +152,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
title={format("library")}
|
||||
displayOnlyOn="1ColumnLayout"
|
||||
className="mb-10"
|
||||
/>
|
||||
|
@ -180,7 +180,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
||||
{item.subitem_of?.data[0]?.attributes && (
|
||||
<div className="grid place-items-center">
|
||||
<p>{langui.subitem_of}</p>
|
||||
<p>{format("subitem_of_x", { x: "" })}</p>
|
||||
<Button
|
||||
href={`/library/${item.subitem_of.data[0].attributes.slug}`}
|
||||
text={prettyInlineTitle(
|
||||
|
@ -212,7 +212,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
<>
|
||||
{item.urls?.length ? (
|
||||
<div className="flex flex-row place-items-center gap-3">
|
||||
<p>{langui.available_at}</p>
|
||||
<p>{format("available_at")}</p>
|
||||
{filterHasAttributes(item.urls, ["url"] as const).map((url, index) => (
|
||||
<Fragment key={index}>
|
||||
<Button href={url.url} text={prettyURL(url.url)} alwaysNewTab />
|
||||
|
@ -220,7 +220,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p>{langui.item_not_available}</p>
|
||||
<p>{format("item_not_available")}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
@ -229,7 +229,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.gallery && item.gallery.data.length > 0 && (
|
||||
<div id={intersectionIds[1]} className="grid w-full place-items-center gap-8">
|
||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||
<h2 className="text-2xl">{format("gallery")}</h2>
|
||||
<div
|
||||
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
||||
gap-8">
|
||||
|
@ -262,7 +262,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
<InsetBox id={intersectionIds[2]} className="grid place-items-center">
|
||||
<div className="place-items grid w-[clamp(0px,100%,42rem)] gap-10">
|
||||
<h2 className="text-center text-2xl">{langui.details}</h2>
|
||||
<h2 className="text-center text-2xl">{format("details")}</h2>
|
||||
<div
|
||||
className={cJoin(
|
||||
"grid place-items-center gap-y-8",
|
||||
|
@ -270,9 +270,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
)}>
|
||||
{item.metadata?.[0] && (
|
||||
<div className="grid place-content-start place-items-center">
|
||||
<h3 className="text-xl">{langui.type}</h3>
|
||||
<h3 className="text-xl">{format("type", { count: 1 })}</h3>
|
||||
<div className="grid grid-flow-col gap-1">
|
||||
<Chip text={prettyItemType(item.metadata[0], langui)} />
|
||||
<Chip text={formatLibraryItemType(item.metadata[0])} />
|
||||
{"›"}
|
||||
<Chip text={prettyItemSubType(item.metadata[0])} />
|
||||
</div>
|
||||
|
@ -281,14 +281,14 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.release_date && (
|
||||
<div className="grid place-content-start place-items-center">
|
||||
<h3 className="text-xl">{langui.release_date}</h3>
|
||||
<h3 className="text-xl">{format("release_date")}</h3>
|
||||
<p>{prettyDate(item.release_date, router.locale)}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.price && (
|
||||
<div className="grid place-content-start place-items-center text-center">
|
||||
<h3 className="text-xl">{langui.price}</h3>
|
||||
<h3 className="text-xl">{format("price")}</h3>
|
||||
<p>
|
||||
{prettyPrice(
|
||||
item.price,
|
||||
|
@ -299,7 +299,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
{item.price.currency?.data?.attributes?.code !== currency && (
|
||||
<p>
|
||||
{prettyPrice(item.price, currencies, currency)} <br />(
|
||||
{langui.calculated?.toLowerCase()})
|
||||
{format("calculated").toLowerCase()})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
@ -308,7 +308,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.categories && item.categories.data.length > 0 && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<h3 className="text-xl">
|
||||
{format("category", { count: item.categories.data.length })}
|
||||
</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(item.categories.data, ["attributes"] as const).map(
|
||||
(category) => (
|
||||
|
@ -325,7 +327,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"grid gap-4",
|
||||
cIf(!isContentPanelAtLeast3xl, "place-items-center")
|
||||
)}>
|
||||
<h3 className="text-xl">{langui.size}</h3>
|
||||
<h3 className="text-xl">{format("size")}</h3>
|
||||
<div
|
||||
className={cJoin(
|
||||
"grid w-full",
|
||||
|
@ -344,7 +346,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"place-items-center"
|
||||
)
|
||||
)}>
|
||||
<p className="font-bold">{langui.width}:</p>
|
||||
<p className="font-bold">{format("width")}:</p>
|
||||
<div>
|
||||
<p>{item.size.width} mm</p>
|
||||
<p>{convertMmToInch(item.size.width)} in</p>
|
||||
|
@ -359,7 +361,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"place-items-center"
|
||||
)
|
||||
)}>
|
||||
<p className="font-bold">{langui.height}:</p>
|
||||
<p className="font-bold">{format("height")}:</p>
|
||||
<div>
|
||||
<p>{item.size.height} mm</p>
|
||||
<p>{convertMmToInch(item.size.height)} in</p>
|
||||
|
@ -375,7 +377,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"place-items-center"
|
||||
)
|
||||
)}>
|
||||
<p className="font-bold">{langui.thickness}:</p>
|
||||
<p className="font-bold">{format("thickness")}:</p>
|
||||
<div>
|
||||
<p>{item.size.thickness} mm</p>
|
||||
<p>{convertMmToInch(item.size.thickness)} in</p>
|
||||
|
@ -393,44 +395,53 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"grid gap-4",
|
||||
cIf(!isContentPanelAtLeast3xl, "place-items-center")
|
||||
)}>
|
||||
<h3 className="text-xl">{langui.type_information}</h3>
|
||||
<h3 className="text-xl">{format("type_information")}</h3>
|
||||
<div className="flex flex-wrap place-content-between gap-x-8">
|
||||
{item.metadata?.[0]?.__typename === "ComponentMetadataBooks" && (
|
||||
<>
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.pages}:</p>
|
||||
<p>{item.metadata[0].page_count}</p>
|
||||
</div>
|
||||
{isDefined(item.metadata[0].page_count) && (
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{format("page", { count: Infinity })}:</p>
|
||||
<p>{item.metadata[0].page_count}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.binding}:</p>
|
||||
<p className="font-bold">{format("binding")}:</p>
|
||||
<p>
|
||||
{item.metadata[0].binding_type ===
|
||||
Enum_Componentmetadatabooks_Binding_Type.Paperback
|
||||
? langui.paperback
|
||||
? format("paperback")
|
||||
: item.metadata[0].binding_type ===
|
||||
Enum_Componentmetadatabooks_Binding_Type.Hardcover
|
||||
? langui.hardcover
|
||||
? format("hardcover")
|
||||
: ""}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.page_order}:</p>
|
||||
<p className="font-bold">{format("page_order")}:</p>
|
||||
<p>
|
||||
{item.metadata[0].page_order ===
|
||||
Enum_Componentmetadatabooks_Page_Order.LeftToRight
|
||||
? langui.left_to_right
|
||||
: langui.right_to_left}
|
||||
? format("left_to_right")
|
||||
: format("right_to_left")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">{langui.languages}:</p>
|
||||
{item.metadata[0]?.languages?.data.map((lang) => (
|
||||
<p key={lang.attributes?.code}>{lang.attributes?.name}</p>
|
||||
))}
|
||||
</div>
|
||||
{isDefined(item.metadata[0].languages) && (
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<p className="font-bold">
|
||||
{format("language", {
|
||||
count: item.metadata[0].languages.data.length,
|
||||
})}
|
||||
:
|
||||
</p>
|
||||
{item.metadata[0].languages.data.map((lang) => (
|
||||
<p key={lang.attributes?.code}>{lang.attributes?.name}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -441,10 +452,12 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.subitems && item.subitems.data.length > 0 && (
|
||||
<div id={intersectionIds[3]} className="grid w-full place-items-center gap-8">
|
||||
<h2 className="text-2xl">{isVariantSet ? langui.variants : langui.subitems}</h2>
|
||||
<h2 className="text-2xl">
|
||||
{format(isVariantSet ? "variant" : "subitem", { count: Infinity })}
|
||||
</h2>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
@ -493,10 +506,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{item.contents && item.contents.data.length > 0 && (
|
||||
<div id={intersectionIds[4]} className="grid w-full place-items-center gap-8">
|
||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
||||
<h2 className="-mb-6 text-2xl">{format("contents")}</h2>
|
||||
{displayOpenScans && (
|
||||
<div className="grid grid-flow-col gap-4">
|
||||
<Button href={`/library/${item.slug}/reader`} text={langui.view_scans} />
|
||||
<Button href={`/library/${item.slug}/reader`} text={format("view_scans")} />
|
||||
</div>
|
||||
)}
|
||||
<div className="max-w- grid w-full gap-4">
|
||||
|
@ -576,7 +589,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const description = getDescription(
|
||||
item.libraryItems.data[0].attributes.descriptions?.[0]?.description,
|
||||
{
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[langui.category ?? "Categories"]: filterHasAttributes(
|
||||
item.libraryItems.data[0].attributes.categories?.data,
|
||||
["attributes.short"]
|
||||
).map((category) => category.attributes.short),
|
||||
|
@ -651,7 +664,7 @@ const ContentLine = ({
|
|||
parentSlug,
|
||||
condensed,
|
||||
}: ContentLineProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const { value: isOpened, toggle: toggleOpened } = useBoolean(false);
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: content?.translations ?? [],
|
||||
|
@ -692,15 +705,15 @@ const ContentLine = ({
|
|||
{hasScanSet && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/reader?page=${rangeStart}`}
|
||||
text={langui.view_scans}
|
||||
text={format("view_scans")}
|
||||
/>
|
||||
)}
|
||||
{isDefined(content) && (
|
||||
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||
<Button href={`/contents/${content.slug}`} text={format("open_content")} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
langui.content_is_not_available
|
||||
format("content_is_not_available")
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -747,15 +760,15 @@ const ContentLine = ({
|
|||
{hasScanSet && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/reader?page=${rangeStart}`}
|
||||
text={langui.view_scans}
|
||||
text={format("view_scans")}
|
||||
/>
|
||||
)}
|
||||
{isDefined(content) && (
|
||||
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||
<Button href={`/contents/${content.slug}`} text={format("open_content")} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
langui.content_is_not_available
|
||||
format("content_is_not_available")
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { getStatusDescription, sortRangedContent } from "helpers/others";
|
||||
import { sortRangedContent } from "helpers/others";
|
||||
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
|
@ -40,6 +40,7 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings";
|
||||
import { useIsWebkit } from "hooks/useIsWebkit";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
type BookType = "book" | "manga";
|
||||
type DisplayMode = "double" | "single";
|
||||
|
@ -96,7 +97,7 @@ const LibrarySlug = ({
|
|||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const isDarkMode = useAtomGetter(atoms.settings.darkMode);
|
||||
const {
|
||||
filterSettings,
|
||||
|
@ -278,34 +279,34 @@ const LibrarySlug = ({
|
|||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton title={langui.item} href={`/library/${itemSlug}`} />
|
||||
<ReturnButton title={format("item", { count: Infinity })} href={`/library/${itemSlug}`} />
|
||||
|
||||
<div className="mt-4 grid gap-2">
|
||||
<WithLabel label={langui.paper_texture}>
|
||||
<WithLabel label={format("paper_texture")}>
|
||||
<Switch value={filterSettings.paperTexture} onClick={togglePaperTexture} />
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.book_fold}>
|
||||
<WithLabel label={format("book_fold")}>
|
||||
<Switch value={filterSettings.bookFold} onClick={toggleBookFold} />
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.lighting}>
|
||||
<WithLabel label={format("lighting")}>
|
||||
<Switch value={filterSettings.lighting} onClick={toggleLighting} />
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.side_pages}>
|
||||
<WithLabel label={format("side_pages")}>
|
||||
<Switch value={isSidePagesEnabled} onClick={toggleIsSidePagesEnabled} />
|
||||
</WithLabel>
|
||||
|
||||
{!isWebkit && (
|
||||
<WithLabel label={langui.shadow}>
|
||||
<WithLabel label={format("shadow")}>
|
||||
<Switch value={filterSettings.dropShadow} onClick={toggleDropShadow} />
|
||||
</WithLabel>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid">
|
||||
<p>{langui.night_reader}:</p>
|
||||
<p>{format("night_reader")}:</p>
|
||||
<Slider
|
||||
min={0}
|
||||
max={10}
|
||||
|
@ -318,18 +319,18 @@ const LibrarySlug = ({
|
|||
</div>
|
||||
|
||||
<div className="mt-8 grid gap-2">
|
||||
<p>{langui.reading_layout}:</p>
|
||||
<p>{format("reading_layout")}:</p>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
icon: "description",
|
||||
tooltip: langui.single_page_view,
|
||||
tooltip: format("single_page_view"),
|
||||
active: displayMode === "single",
|
||||
onClick: () => changeDisplayMode("single"),
|
||||
},
|
||||
{
|
||||
icon: "auto_stories",
|
||||
tooltip: langui.double_page_view,
|
||||
tooltip: format("double_page_view"),
|
||||
active: displayMode === "double",
|
||||
onClick: () => changeDisplayMode("double"),
|
||||
},
|
||||
|
@ -338,7 +339,7 @@ const LibrarySlug = ({
|
|||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2">
|
||||
<p>{langui.quality}:</p>
|
||||
<p>{format("quality")}:</p>
|
||||
<ButtonGroup
|
||||
buttonsProps={[
|
||||
{
|
||||
|
@ -357,7 +358,7 @@ const LibrarySlug = ({
|
|||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_options}
|
||||
text={format("reset_all_options")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
resetReaderSettings();
|
||||
|
@ -787,7 +788,7 @@ interface ScanSetProps {
|
|||
|
||||
const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format, formatStatusDescription } = useFormat();
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: scanSet,
|
||||
languageExtractor: useCallback(
|
||||
|
@ -844,8 +845,8 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
|||
text={
|
||||
selectedScan.language?.data?.attributes?.code ===
|
||||
selectedScan.source_language?.data?.attributes?.code
|
||||
? langui.scan ?? "Scan"
|
||||
: langui.scanlation ?? "Scanlation"
|
||||
? format("scan")
|
||||
: format("scanlation")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
@ -854,7 +855,7 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
|||
{content?.data?.attributes && isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||
<Button
|
||||
href={`/contents/${content.data.attributes.slug}`}
|
||||
text={langui.open_content}
|
||||
text={format("open_content")}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -863,17 +864,15 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
|||
)}
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}>
|
||||
<p className="font-headers font-bold">{format("status")}:</p>
|
||||
<ToolTip content={formatStatusDescription(selectedScan.status)} maxWidth={"20rem"}>
|
||||
<Chip text={selectedScan.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.scanners}:</p>
|
||||
<p className="font-headers font-bold">{format("scanners")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data, [
|
||||
"id",
|
||||
|
@ -889,7 +888,7 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
|||
|
||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.cleaners}:</p>
|
||||
<p className="font-headers font-bold">{format("cleaners")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.cleaners.data, [
|
||||
"id",
|
||||
|
@ -905,7 +904,7 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
|||
|
||||
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
||||
<p className="font-headers font-bold">{format("typesetters")}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
|
@ -921,7 +920,7 @@ const ScanSet = ({ onClickOnImage, scanSet, id, title, content }: ScanSetProps):
|
|||
|
||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||
<ToolTip content={selectedScan.notes}>
|
||||
<Chip text={langui.notes ?? "Notes"} />
|
||||
<Chip text={format("notes")} />
|
||||
</ToolTip>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -25,8 +25,6 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { MeiliIndices, MeiliLibraryItem } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
|
@ -36,6 +34,7 @@ import { isUntangibleGroupItem } from "helpers/libraryItem";
|
|||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||
import { useLibraryItemUserStatus } from "hooks/useLibraryItemUserStatus";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -72,16 +71,16 @@ interface Props extends AppLayoutRequired {}
|
|||
|
||||
const Library = (props: Props): JSX.Element => {
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const { libraryItemUserStatus } = useLibraryItemUserStatus();
|
||||
|
||||
const sortingMethods = useMemo(
|
||||
() => [
|
||||
{ meiliAttribute: "sortable_name:asc", displayedName: langui.name },
|
||||
{ meiliAttribute: "sortable_date:asc", displayedName: langui.release_date },
|
||||
{ meiliAttribute: "sortable_price:asc", displayedName: langui.price },
|
||||
{ meiliAttribute: "sortable_name:asc", displayedName: format("name") },
|
||||
{ meiliAttribute: "sortable_date:asc", displayedName: format("release_date") },
|
||||
{ meiliAttribute: "sortable_price:asc", displayedName: format("price") },
|
||||
],
|
||||
[langui.name, langui.price, langui.release_date]
|
||||
[format]
|
||||
);
|
||||
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
|
@ -249,15 +248,15 @@ const Library = (props: Props): JSX.Element => {
|
|||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="auto_stories"
|
||||
title={langui.library}
|
||||
description={langui.library_description}
|
||||
title={format("library")}
|
||||
description={format("library_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title ?? "Search..."}
|
||||
placeholder={format("search_title")}
|
||||
value={query}
|
||||
onChange={(name) => {
|
||||
setPage(1);
|
||||
|
@ -270,10 +269,10 @@ const Library = (props: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={langui.order_by}>
|
||||
<WithLabel label={format("order_by")}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={sortingMethods.map((item) => item.displayedName ?? "")}
|
||||
options={sortingMethods.map((item) => item.displayedName)}
|
||||
value={sortingMethod}
|
||||
onChange={(newSort) => {
|
||||
setPage(1);
|
||||
|
@ -286,7 +285,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.show_subitems}>
|
||||
<WithLabel label={format("show_subitems")}>
|
||||
<Switch
|
||||
value={showSubitems}
|
||||
onClick={() => {
|
||||
|
@ -297,7 +296,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.show_primary_items}>
|
||||
<WithLabel label={format("show_primary_items")}>
|
||||
<Switch
|
||||
value={showPrimaryItems}
|
||||
onClick={() => {
|
||||
|
@ -308,7 +307,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={langui.show_secondary_items}>
|
||||
<WithLabel label={format("show_secondary_items")}>
|
||||
<Switch
|
||||
value={showSecondaryItems}
|
||||
onClick={() => {
|
||||
|
@ -320,7 +319,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
|
@ -335,7 +334,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
className="mt-4"
|
||||
buttonsProps={[
|
||||
{
|
||||
tooltip: langui.only_display_items_i_want,
|
||||
tooltip: format("only_display_items_i_want"),
|
||||
icon: "favorite",
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
|
@ -345,7 +344,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
active: filterUserStatus === LibraryItemUserStatus.Want,
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_items_i_have,
|
||||
tooltip: format("only_display_items_i_have"),
|
||||
icon: "back_hand",
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
|
@ -355,7 +354,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
active: filterUserStatus === LibraryItemUserStatus.Have,
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_unmarked_items,
|
||||
tooltip: format("only_display_unmarked_items"),
|
||||
icon: "nearby_off",
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
|
@ -365,8 +364,8 @@ const Library = (props: Props): JSX.Element => {
|
|||
active: filterUserStatus === LibraryItemUserStatus.None,
|
||||
},
|
||||
{
|
||||
tooltip: langui.only_display_unmarked_items,
|
||||
text: langui.all,
|
||||
tooltip: format("only_display_unmarked_items"),
|
||||
text: format("all"),
|
||||
onClick: () => {
|
||||
setPage(1);
|
||||
setFilterUserStatus(undefined);
|
||||
|
@ -379,7 +378,7 @@ const Library = (props: Props): JSX.Element => {
|
|||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
|
|
|
@ -4,8 +4,7 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
|||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -14,12 +13,16 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
|
||||
interface Props extends AppLayoutRequired {}
|
||||
const Merch = (props: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={
|
||||
<SubPanel>
|
||||
<PanelHeader icon="store" title={langui.merch} description={langui.merch_description} />
|
||||
<PanelHeader
|
||||
icon="store"
|
||||
title={format("merch")}
|
||||
description={format("merch_description")}
|
||||
/>
|
||||
</SubPanel>
|
||||
}
|
||||
{...props}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { prettyTerminalBoxedTitle } from "helpers/terminal";
|
|||
import { prettyMarkdown } from "helpers/description";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
/*
|
||||
* ╭────────╮
|
||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||
|
@ -19,7 +20,7 @@ import { useAtomGetter } from "helpers/atoms";
|
|||
interface Props extends PostStaticProps {}
|
||||
|
||||
const LibrarySlug = (props: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -36,7 +37,7 @@ const LibrarySlug = (props: Props): JSX.Element => {
|
|||
return (
|
||||
<PostPage
|
||||
returnHref="/news"
|
||||
returnTitle={langui.news}
|
||||
returnTitle={format("news")}
|
||||
displayCredits
|
||||
displayThumbnailHeader
|
||||
displayToc
|
||||
|
|
|
@ -30,6 +30,7 @@ import { MeiliIndices, MeiliPost } from "shared/meilisearch-graphql-typings/meil
|
|||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -55,7 +56,7 @@ const queryParamSchema = z.object({
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const News = ({ ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
|
||||
|
@ -115,13 +116,17 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader icon="newspaper" title={langui.news} description={langui.news_description} />
|
||||
<PanelHeader
|
||||
icon="newspaper"
|
||||
title={format("news")}
|
||||
description={format("news_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title ?? "Search..."}
|
||||
placeholder={format("search_title")}
|
||||
value={query}
|
||||
onChange={(name) => {
|
||||
setQuery(name);
|
||||
|
@ -134,7 +139,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
|
@ -147,7 +152,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
|
|
|
@ -24,6 +24,7 @@ import { Terminal } from "components/Cli/Terminal";
|
|||
import { prettyTerminalBoxedTitle, prettyTerminalUnderlinedTitle } from "helpers/terminal";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -35,7 +36,7 @@ interface Props extends AppLayoutRequired {
|
|||
}
|
||||
|
||||
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const router = useRouter();
|
||||
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
|
||||
const { showLightBox } = useAtomGetter(atoms.lightBox);
|
||||
|
@ -51,7 +52,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton href={`/wiki`} title={langui.wiki} displayOnlyOn={"3ColumnsLayout"} />
|
||||
<ReturnButton href={`/wiki`} title={format("wiki")} displayOnlyOn={"3ColumnsLayout"} />
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
|
@ -59,7 +60,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
<ContentPanel width={ContentPanelWidthSizes.Large}>
|
||||
<ReturnButton
|
||||
href={`/wiki`}
|
||||
title={langui.wiki}
|
||||
title={format("wiki")}
|
||||
displayOnlyOn={"1ColumnLayout"}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
@ -98,7 +99,9 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
<div className="my-4 grid gap-4 p-4">
|
||||
{page.categories?.data && page.categories.data.length > 0 && (
|
||||
<>
|
||||
<p className="font-headers text-xl font-bold">{langui.categories}</p>
|
||||
<p className="font-headers text-xl font-bold">
|
||||
{format("category", { count: page.categories.data.length })}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(page.categories.data, ["attributes"] as const).map(
|
||||
|
@ -112,7 +115,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{page.tags?.data && page.tags.data.length > 0 && (
|
||||
<>
|
||||
<p className="font-headers text-xl font-bold">{langui.tags}</p>
|
||||
<p className="font-headers text-xl font-bold">{format("tags")}</p>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(page.tags.data, ["attributes"] as const).map((tag) => (
|
||||
<Chip
|
||||
|
@ -130,7 +133,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
||||
<div className="mb-12">
|
||||
<p className="font-headers text-lg font-bold">{langui.summary}</p>
|
||||
<p className="font-headers text-lg font-bold">{format("summary")}</p>
|
||||
<p>{selectedTranslation.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -188,13 +191,13 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
}`
|
||||
)}${
|
||||
isDefinedAndNotEmpty(selectedTranslation?.summary)
|
||||
? `${prettyTerminalUnderlinedTitle(langui.summary)}${selectedTranslation?.summary}`
|
||||
? `${prettyTerminalUnderlinedTitle(format("summary"))}${selectedTranslation?.summary}`
|
||||
: ""
|
||||
}${
|
||||
page.definitions && page.definitions.length > 0
|
||||
? `${filterHasAttributes(page.definitions, ["translations"] as const).map(
|
||||
(definition, index) =>
|
||||
`${prettyTerminalUnderlinedTitle(`${langui.definition} ${index + 1}`)}${
|
||||
`${prettyTerminalUnderlinedTitle(format("definition_x", { x: index + 1 }))}${
|
||||
staticSmartLanguage({
|
||||
items: filterHasAttributes(definition.translations, [
|
||||
"language.data.attributes.code",
|
||||
|
@ -244,7 +247,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
] as const).map(
|
||||
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
),
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[langui.category ?? "Categories"]: filterHasAttributes(
|
||||
page.wikiPages.data[0].attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
} from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
@ -27,8 +26,7 @@ import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
|||
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -41,7 +39,7 @@ interface Props extends AppLayoutRequired {
|
|||
}
|
||||
|
||||
const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const ids = filterHasAttributes(chronologyEras, ["attributes"] as const).map(
|
||||
(era) => era.attributes.slug
|
||||
);
|
||||
|
@ -50,7 +48,7 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props):
|
|||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton href="/wiki" title={langui.wiki} displayOnlyOn="3ColumnsLayout" />
|
||||
<ReturnButton href="/wiki" title={format("wiki")} displayOnlyOn="3ColumnsLayout" />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -81,7 +79,7 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props):
|
|||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/wiki"
|
||||
title={langui.wiki}
|
||||
title={format("wiki")}
|
||||
displayOnlyOn="1ColumnLayout"
|
||||
className="mb-10"
|
||||
/>
|
||||
|
@ -302,7 +300,7 @@ interface ChronologyEventProps {
|
|||
}
|
||||
|
||||
export const ChronologyEvent = ({ event, id }: ChronologyEventProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format, formatStatusDescription } = useFormat();
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: event.translations ?? [],
|
||||
languageExtractor: useCallback(
|
||||
|
@ -322,7 +320,7 @@ export const ChronologyEvent = ({ event, id }: ChronologyEventProps): JSX.Elemen
|
|||
{selectedTranslation.status !==
|
||||
Enum_Componenttranslationschronologyitem_Status.Done && (
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||
content={formatStatusDescription(selectedTranslation.status)}
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.status} />
|
||||
</ToolTip>
|
||||
|
@ -334,7 +332,7 @@ export const ChronologyEvent = ({ event, id }: ChronologyEventProps): JSX.Elemen
|
|||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<Ico icon="warning" className="!text-sm" />
|
||||
{langui.no_source_warning}
|
||||
{format("no_source_warning")}
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
|
@ -354,7 +352,7 @@ export const ChronologyEvent = ({ event, id }: ChronologyEventProps): JSX.Elemen
|
|||
<p className="whitespace-pre-line">{selectedTranslation.description}</p>
|
||||
)}
|
||||
|
||||
{selectedTranslation.note && <em>{`${langui.notes}: ${selectedTranslation.note}`}</em>}
|
||||
{selectedTranslation.note && <em>{`${format("notes")}: ${selectedTranslation.note}`}</em>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -19,12 +19,11 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { MeiliIndices, MeiliWikiPage } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -51,7 +50,7 @@ interface Props extends AppLayoutRequired {}
|
|||
|
||||
const Wiki = (props: Props): JSX.Element => {
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const { format } = useFormat();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
|
||||
|
@ -103,15 +102,15 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon="travel_explore"
|
||||
title={langui.wiki}
|
||||
description={langui.wiki_description}
|
||||
title={format("wiki")}
|
||||
description={format("wiki_description")}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title ?? "Search..."}
|
||||
placeholder={format("search_title")}
|
||||
value={query}
|
||||
onChange={(name) => {
|
||||
setPage(1);
|
||||
|
@ -125,7 +124,7 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
/>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
|
@ -138,7 +137,7 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
|
||||
<Button
|
||||
className="mt-8"
|
||||
text={langui.reset_all_filters}
|
||||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
|
@ -150,9 +149,9 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
|
||||
<HorizontalLine />
|
||||
|
||||
<p className="mb-4 font-headers text-xl font-bold">{langui.special_pages}</p>
|
||||
<p className="mb-4 font-headers text-xl font-bold">{format("special_pages")}</p>
|
||||
|
||||
<NavOption title={langui.chronology} url="/wiki/chronology" border />
|
||||
<NavOption title={format("chronology")} url="/wiki/chronology" border />
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue