commit
e3e67b8dbc
|
@ -1,4 +1,5 @@
|
|||
src/graphql/generated.ts
|
||||
src/graphql/icuParams.ts
|
||||
src/shared
|
||||
.eslintrc.js
|
||||
graphql-codegen.config.js
|
||||
|
|
120
README.md
120
README.md
|
@ -4,69 +4,128 @@
|
|||
[![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)
|
||||
### Overview
|
||||
|
||||
- CMS: Stapi
|
||||
- GraphQL endpoint
|
||||
- Multilanguage support
|
||||
- Markdown format for the rich text fields
|
||||
- Use webhooks to notify the front-end and image processor of updates
|
||||
![](docs/project-mind-map.png)
|
||||
|
||||
#### [Image Processor](https://github.com/Accords-Library/img.accords-library.com)
|
||||
_Purple connections are actions done at build-time only. Grey connections can be at build-time or run-time._
|
||||
|
||||
- Convert the images from the CMS to 4 formats
|
||||
- Small: 512x512, quality 60, .webp
|
||||
- Medium: 1024x1024, quality 75, .webp
|
||||
- Large: 2048x2048, quality 80, .webp
|
||||
- Og: 512x512, quality 60, .jpg
|
||||
### [strapi.accords-library.com](https://github.com/Accords-Library/strapi.accords-library.com)
|
||||
|
||||
#### [Front](https://github.com/Accords-Library/accords-library.com) (this repository)
|
||||
Our Content Management System (CMS) that uses [Strapi](https://strapi.io/).
|
||||
|
||||
- Use the official [GraphQL plugin](https://market.strapi.io/plugins/@strapi-plugin-graphql)
|
||||
- Multilanguage support
|
||||
- Markdown format for the rich text fields
|
||||
- Use webhooks to notify the front-end, search engine, and image processor when new content/media has been created/modified/deleted
|
||||
|
||||
### [img.accords-library.com](https://github.com/Accords-Library/img.accords-library.com)
|
||||
|
||||
A custom made image processor to overcome the lack of customization offered by Strapi build-in image processor. There is a python script to bulk process all images uploaded to Strapi. Subsequent changes to Strapi's media library can be handled using webhooks. The repo includes a server that listen to these webhook calls, and another to serve the images.
|
||||
|
||||
Each image in Strapi's media library is converted to four different formats:
|
||||
- Small: 512x512, quality 60, .webp
|
||||
- Medium: 1024x1024, quality 75, .webp
|
||||
- Large: 2048x2048, quality 80, .webp
|
||||
- Og: 512x512, quality 60, .jpg
|
||||
|
||||
### [search.accords-library.com](https://github.com/Accords-Library/search.accords-library.com)
|
||||
|
||||
A search engine that uses [Meilisearch](https://www.meilisearch.com/).
|
||||
The repo includes a docker-compose file to run an instance of Meilisearch. There is also a server that populates Meilisearch's documents at startup, then listen to webhooks sent by Strapi for subsequent changes.
|
||||
|
||||
### [gallery.accords-library.com](https://github.com/Accords-Library/gallery.accords-library.com)
|
||||
|
||||
An image board engine, uses [Szurubooru](https://github.com/rr-/szurubooru), a lighweight engine inspired by Danbooru (and Booru-type galleries in general). Unlike the other subdomains, this repo is completely separated from the rest of the stack.
|
||||
|
||||
### [watch.accords-library.com](https://github.com/Accords-Library/watch.accords-library.com)
|
||||
|
||||
A set of tools to archive videos on multiple platforms. The repo contains a CLI tool to archive YouTube videos. There is also a Python script which import the videos metadata to Strapi using GraphQL mutations. Finally, there's a server to serve the video files and thumbnail.
|
||||
|
||||
### [umami.accords-library.com](https://umami.is/)
|
||||
|
||||
An open-source self-hosted alternative to Google Analytics which doesn't require a cookie notice to be GDPR compliant.
|
||||
|
||||
### [accords-library.com](https://github.com/Accords-Library/accords-library.com) (this repository)
|
||||
|
||||
A detailled look at the technologies used in this repository:
|
||||
|
||||
- Language: [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
- Framework: [Next.js 13](https://nextjs.org/) (React 18)
|
||||
|
||||
- SSG + ISR (Static Site Generation + Incremental Static Regeneration)
|
||||
|
||||
- The website is built before running in production
|
||||
- Performances are great, and it's possible 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 pages.
|
||||
|
||||
- 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!
|
||||
- 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 creating any arbitrary theme 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- 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.
|
||||
|
||||
- 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 a specific working isn't available in the user's language
|
||||
|
||||
- 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)
|
||||
|
@ -75,7 +134,11 @@
|
|||
|
||||
- Other
|
||||
- Custom book reader based on [Okuma-Reader](https://github.com/DrMint/Okuma-Reader)
|
||||
- Support for [Material Symbols](https://fonts.google.com/icons)
|
||||
- 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 secret "Terminal" mode. Can you find it?
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -91,9 +154,9 @@ Create a env file:
|
|||
nano .env.local
|
||||
```
|
||||
|
||||
Enter the followind information:
|
||||
Enter the following information:
|
||||
|
||||
```txt
|
||||
```
|
||||
URL_GRAPHQL=https://url-to.strapi-accords-library.com/graphql
|
||||
ACCESS_TOKEN=abcdef0123456789
|
||||
REVALIDATION_TOKEN=abcdef0123456789
|
||||
|
@ -104,7 +167,8 @@ NEXT_PUBLIC_URL_CMS=https://url-to.strapi-accords-library.com
|
|||
NEXT_PUBLIC_URL_IMG=https://url-to.img-accords-library.com
|
||||
NEXT_PUBLIC_URL_WATCH=https://url-to.watch-accords-library.com
|
||||
NEXT_PUBLIC_URL_SELF=https://url-to-front-accords-library.com
|
||||
NEXT_PUBLIC_URL_SEARCH=https://url-to.search-accords-library.com
|
||||
NEXT_PUBLIC_URL_MEILISEARCH=https://url-to.search-accords-library.com
|
||||
NEXT_PUBLIC_MEILISEARCH_KEY=abcdef0123456789
|
||||
NEXT_PUBLIC_UMAMI_URL=https://url-to.umami-accords-library.com
|
||||
NEXT_PUBLIC_UMAMI_ID=abcdef0123456789
|
||||
```
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0"?>
|
||||
<minder version="1.14.0" parent-etag="2844169042" etag="3777682473">
|
||||
<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="-736.36765543619742" y="32.05864461263036" scale="1.5"/>
|
||||
<images/>
|
||||
<nodes>
|
||||
<node id="0" posx="648.76816813151015" posy="501.13512166341127" 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="667.76816813151015" posy="520.13512166341127" maxwidth="200">
|
||||
<text data="accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="1" posx="603.1911417643222" posy="198.59891764322904" 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="622.1911417643222" posy="217.59891764322904" maxwidth="200">
|
||||
<text data="strapi.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="2" posx="508.77593994140574" posy="358.0493469238279" 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="527.7759399414058" posy="377.0493469238279" maxwidth="200">
|
||||
<text data="watch.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="3" posx="959.78491210937489" posy="490.81981404622388" 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="978.78491210937489" posy="509.81981404622388" maxwidth="200">
|
||||
<text data="search.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="4" posx="300.46666463216098" posy="474.56320190429688" 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="319.46666463216098" posy="493.56320190429688" maxwidth="200">
|
||||
<text data="img.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="5" posx="753.05198160807242" posy="709.79446411132812" 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="772.05198160807242" posy="728.79446411132812" maxwidth="200">
|
||||
<text data="umami.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
<node id="6" posx="468.00632731119703" posy="709.85610961914062" 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="487.00632731119703" posy="728.85610961914062" maxwidth="200">
|
||||
<text data="gallery.accords-library.com"/>
|
||||
</nodename>
|
||||
<nodenote></nodenote>
|
||||
</node>
|
||||
</nodes>
|
||||
<groups/>
|
||||
<connections>
|
||||
<connection from_id="3" to_id="1" drag_x="1038.6269124348951" drag_y="296.62844848632801" 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="982.0184326171875" drag_y="347.55404663085926">
|
||||
<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="640.25118001302098" drag_y="308.37991333007801" 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="801.02655029296898" drag_y="644.59735107421943">
|
||||
<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="531.34985351562477" drag_y="582.42803955078148">
|
||||
<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="917.41172281901027" drag_y="585.41107177734443">
|
||||
<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="872.998291015625" drag_y="408.14896647135413">
|
||||
<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="664.23213704427053" drag_y="645.67006429036542">
|
||||
<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="400.83854166666663" drag_y="295.4715677897135" 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="645.96777343749955" drag_y="448.91068522135413">
|
||||
<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="441.14660644531227" drag_y="342.38249715169252">
|
||||
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 9" connectiontwidth="100"/>
|
||||
<title>Webhook</title>
|
||||
<note></note>
|
||||
</connection>
|
||||
<connection from_id="1" to_id="0" drag_x="790.7373046875" drag_y="369.4803466796875">
|
||||
<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 unused-exports && npm run prettier && 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]);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { getReadySdk } from "./sdk";
|
||||
import { getLangui } from "./fetchLocalData";
|
||||
import { PostWithTranslations } from "types/types";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||
|
@ -8,6 +7,7 @@ import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/local
|
|||
import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { AppLayoutRequired } from "components/AppLayout";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
export interface PostStaticProps extends AppLayoutRequired {
|
||||
post: PostWithTranslations;
|
||||
|
@ -17,7 +17,7 @@ export const getPostStaticProps =
|
|||
(slug: string): GetStaticProps =>
|
||||
async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const post = await sdk.getPost({
|
||||
slug: slug,
|
||||
language_code: context.locale ?? "en",
|
||||
|
@ -38,10 +38,8 @@ export const getPostStaticProps =
|
|||
const title = selectedTranslation?.title ?? prettySlug(slug);
|
||||
|
||||
const description = getDescription(selectedTranslation?.excerpt, {
|
||||
[langui.release_date ?? "Release date"]: [
|
||||
prettyDate(post.posts.data[0].attributes.date, context.locale),
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[format("release_date")]: [prettyDate(post.posts.data[0].attributes.date, context.locale)],
|
||||
[format("category", { count: Infinity })]: filterHasAttributes(
|
||||
post.posts.data[0].attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
|
@ -53,7 +51,7 @@ export const getPostStaticProps =
|
|||
|
||||
const props: PostStaticProps = {
|
||||
post: post.posts.data[0].attributes as PostWithTranslations,
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
||||
openGraph: getOpenGraph(format, title, description, thumbnail),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import { IntlMessageFormat } from "intl-messageformat";
|
||||
import { LibraryItemMetadataDynamicZone } from "graphql/generated";
|
||||
import { ICUParams } from "graphql/icuParams";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
|
||||
type WordingKey = keyof ICUParams;
|
||||
|
||||
type LibraryItemType = Exclude<LibraryItemMetadataDynamicZone["__typename"], undefined>;
|
||||
|
||||
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 getFormat = (
|
||||
locale: string | undefined
|
||||
): {
|
||||
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 = getLangui(locale);
|
||||
const fallbackLangui = getLangui("en");
|
||||
|
||||
const format = (
|
||||
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();
|
||||
};
|
||||
|
||||
const formatLibraryItemType = (metadata: { __typename: LibraryItemType }): string =>
|
||||
format(componentMetadataToWording[metadata.__typename]);
|
||||
|
||||
const formatStatusLabel = (status: ContentStatus): string =>
|
||||
format(componentSetsTextsetStatusToWording[status].label);
|
||||
|
||||
const formatStatusDescription = (status: ContentStatus): string =>
|
||||
format(componentSetsTextsetStatusToWording[status].description);
|
||||
|
||||
return {
|
||||
format,
|
||||
formatLibraryItemType,
|
||||
formatStatusLabel,
|
||||
formatStatusDescription,
|
||||
};
|
||||
};
|
|
@ -1,7 +1,8 @@
|
|||
import { OgImage, getImgSizesByQuality, ImageQuality, getAssetURL } from "./img";
|
||||
import { isDefinedAndNotEmpty } from "./asserts";
|
||||
import { Langui } from "./localData";
|
||||
import { getFormat } from "./i18n";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
const DEFAULT_OG_THUMBNAIL = {
|
||||
image: `${process.env.NEXT_PUBLIC_URL_SELF}/default_og.jpg`,
|
||||
|
@ -20,13 +21,13 @@ export interface OpenGraph {
|
|||
}
|
||||
|
||||
export const getOpenGraph = (
|
||||
langui: Langui,
|
||||
format: ReturnType<typeof getFormat>["format"] | ReturnType<typeof useFormat>["format"],
|
||||
title?: string | null | undefined,
|
||||
description?: string | null | undefined,
|
||||
thumbnail?: UploadImageFragment | null | undefined
|
||||
): OpenGraph => ({
|
||||
title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""}`,
|
||||
description: isDefinedAndNotEmpty(description) ? description : langui.default_description ?? "",
|
||||
description: isDefinedAndNotEmpty(description) ? description : format("default_description"),
|
||||
thumbnail: thumbnail ? getOgImage(thumbnail) : DEFAULT_OG_THUMBNAIL,
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -3,10 +3,9 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
|||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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>
|
||||
|
@ -44,9 +43,9 @@ export default FourOhFour;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, `404 - ${langui.page_not_found}`),
|
||||
openGraph: getOpenGraph(format, `404 - ${format("page_not_found")}`),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -3,10 +3,9 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
|||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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>
|
||||
|
@ -44,9 +43,10 @@ export default FiveHundred;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, "500 - Internal Server Error"),
|
||||
/* TODO: Langui */
|
||||
openGraph: getOpenGraph(format, "500 - Internal Server Error"),
|
||||
};
|
||||
return {
|
||||
props: 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 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
|
||||
|
|
|
@ -5,9 +5,8 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
|||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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}
|
||||
|
@ -48,9 +47,9 @@ export default AboutUs;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.about_us ?? "About us"),
|
||||
openGraph: getOpenGraph(format, format("about_us")),
|
||||
};
|
||||
return {
|
||||
props: 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
|
||||
/>
|
||||
|
|
|
@ -5,9 +5,8 @@ import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
|||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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>
|
||||
|
@ -36,9 +39,9 @@ export default Archives;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.archives ?? "Archives"),
|
||||
openGraph: getOpenGraph(format, format("archives")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -13,9 +13,6 @@ import { SubPanel } from "components/Containers/SubPanel";
|
|||
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 +25,8 @@ 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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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);
|
||||
|
@ -281,7 +275,7 @@ export default Channel;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const channel = await sdk.getVideoChannel({
|
||||
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
|
||||
});
|
||||
|
@ -289,7 +283,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const props: Props = {
|
||||
channel: channel.videoChannels.data[0].attributes,
|
||||
openGraph: getOpenGraph(langui, channel.videoChannels.data[0].attributes.title),
|
||||
openGraph: getOpenGraph(format, channel.videoChannels.data[0].attributes.title),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -13,9 +13,6 @@ import { SubPanel } from "components/Containers/SubPanel";
|
|||
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 +23,8 @@ 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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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);
|
||||
|
@ -271,9 +267,9 @@ export default Videos;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.videos ?? "Videos"),
|
||||
openGraph: getOpenGraph(format, format("videos")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -15,10 +15,11 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
|||
import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||
import { getVideoFile } from "helpers/videos";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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>
|
||||
|
@ -143,7 +144,7 @@ export default Video;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const videos = await sdk.getVideo({
|
||||
uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
|
||||
});
|
||||
|
@ -151,7 +152,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const props: Props = {
|
||||
video: videos.videos.data[0].attributes,
|
||||
openGraph: getOpenGraph(langui, videos.videos.data[0].attributes.title),
|
||||
openGraph: getOpenGraph(format, videos.videos.data[0].attributes.title),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -17,11 +17,10 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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 />
|
||||
|
||||
|
@ -160,7 +163,7 @@ export default Chronicle;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const slug =
|
||||
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||
const chronicle = await sdk.getChronicle({
|
||||
|
@ -190,11 +193,11 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
selectedContentTranslation.subtitle
|
||||
),
|
||||
description: getDescription(selectedContentTranslation.description, {
|
||||
[langui.type ?? "Type"]: [
|
||||
[format("type", { count: Infinity })]: [
|
||||
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.type?.data
|
||||
?.attributes?.titles?.[0]?.title,
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[format("category", { count: Infinity })]: filterHasAttributes(
|
||||
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.categories
|
||||
?.data,
|
||||
["attributes"] as const
|
||||
|
@ -231,7 +234,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const props: Props = {
|
||||
chronicle: chronicle.chronicles.data[0].attributes as ChronicleWithTranslations,
|
||||
chapters: chronicles.chroniclesChapters.data,
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
||||
openGraph: getOpenGraph(format, title, description, thumbnail),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -9,9 +9,8 @@ import { prettySlug } from "helpers/formatters";
|
|||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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 />
|
||||
|
@ -63,13 +62,13 @@ export default Chronicles;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const chronicles = await sdk.getChroniclesChapters();
|
||||
if (!chronicles.chroniclesChapters?.data) return { notFound: true };
|
||||
|
||||
const props: Props = {
|
||||
chapters: chronicles.chroniclesChapters.data,
|
||||
openGraph: getOpenGraph(langui, langui.chronicles ?? "Chronicles"),
|
||||
openGraph: getOpenGraph(format, format("chronicles")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -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";
|
||||
|
@ -31,10 +30,11 @@ import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/local
|
|||
import { getDescription } from "helpers/description";
|
||||
import { TranslatedPreviewLine } from "components/PreviewLine";
|
||||
import { cIf } from "helpers/className";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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, [
|
||||
|
@ -375,7 +374,7 @@ export default Content;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
||||
const content = await sdk.getContentText({
|
||||
slug: slug,
|
||||
|
@ -401,10 +400,10 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
selectedTranslation.subtitle
|
||||
),
|
||||
description: getDescription(selectedTranslation.description, {
|
||||
[langui.type ?? "Type"]: [
|
||||
[format("type", { count: Infinity })]: [
|
||||
content.contents.data[0].attributes.type?.data?.attributes?.titles?.[0]?.title,
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[format("category", { count: Infinity })]: filterHasAttributes(
|
||||
content.contents.data[0].attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
|
@ -426,7 +425,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const props: Props = {
|
||||
content: content.contents.data[0].attributes as ContentWithTranslations,
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
||||
openGraph: getOpenGraph(format, title, description, thumbnail),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -20,16 +20,15 @@ import {
|
|||
} from "helpers/asserts";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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);
|
||||
|
@ -254,10 +253,10 @@ export default Contents;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.contents ?? "Contents"),
|
||||
openGraph: getOpenGraph(format, format("contents")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -16,10 +16,11 @@ import { SubPanel } from "components/Containers/SubPanel";
|
|||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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 && (
|
||||
|
@ -186,7 +187,7 @@ export default ContentsFolder;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const slug = context.params?.slug ? context.params.slug.toString() : "";
|
||||
const contentsFolder = await sdk.getContentsFolder({
|
||||
slug: slug,
|
||||
|
@ -208,7 +209,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const title = (() => {
|
||||
if (slug === "root") {
|
||||
return langui.contents ?? "Contents";
|
||||
return format("contents");
|
||||
}
|
||||
if (context.locale && context.locales) {
|
||||
const selectedTranslation = staticSmartLanguage({
|
||||
|
@ -224,7 +225,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
})();
|
||||
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, title),
|
||||
openGraph: getOpenGraph(format, title),
|
||||
folder,
|
||||
};
|
||||
return {
|
||||
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -9,8 +9,8 @@ import { getReadySdk } from "graphql/sdk";
|
|||
import { filterDefined, filterHasAttributes } from "helpers/asserts";
|
||||
import { Report, Severity } from "types/Report";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sJoin } from "helpers/formatters";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -83,11 +83,11 @@ export default CheckupContents;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const contents = await sdk.devGetContents();
|
||||
const props: Props = {
|
||||
contents: contents,
|
||||
openGraph: getOpenGraph(langui, "Checkup Contents"),
|
||||
openGraph: getOpenGraph(format, "Checkup Contents"),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -11,8 +11,8 @@ import {
|
|||
import { getReadySdk } from "graphql/sdk";
|
||||
import { Report, Severity } from "types/Report";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sJoin } from "helpers/formatters";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -85,12 +85,12 @@ export default CheckupLibraryItems;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const libraryItems = await sdk.devGetLibraryItems();
|
||||
|
||||
const props: Props = {
|
||||
libraryItems: libraryItems,
|
||||
openGraph: getOpenGraph(langui, "Checkup Library Items"),
|
||||
openGraph: getOpenGraph(format, "Checkup Library Items"),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/Cont
|
|||
import { Popup } from "components/Containers/Popup";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -404,9 +404,9 @@ export default Editor;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, "Markdawn Editor"),
|
||||
openGraph: getOpenGraph(format, "Markdawn Editor"),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { GetStaticProps } from "next";
|
|||
import { ReactNode, useState } from "react";
|
||||
import Slider from "rc-slider";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
@ -19,6 +18,7 @@ import { PreviewCard } from "components/PreviewCard";
|
|||
import { PreviewLine } from "components/PreviewLine";
|
||||
import { ChroniclePreview } from "components/Chronicles/ChroniclePreview";
|
||||
import { PreviewFolder } from "components/Contents/PreviewFolder";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -880,9 +880,9 @@ export default DesignSystem;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, "Design System"),
|
||||
openGraph: getOpenGraph(format, "Design System"),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
|||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -533,9 +533,9 @@ export default Transcript;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, "Japanese Transcription Tool"),
|
||||
openGraph: getOpenGraph(format, "Japanese Transcription Tool"),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { Terminal } from "components/Cli/Terminal";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { PostWithTranslations } from "types/types";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const Home = (props: PostStaticProps): JSX.Element => {
|
||||
const { format } = useFormat();
|
||||
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
|
||||
|
||||
if (isTerminalMode) {
|
||||
|
@ -34,7 +39,7 @@ const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => {
|
|||
|
||||
return (
|
||||
<PostPage
|
||||
{...otherProps}
|
||||
{...props}
|
||||
prependBody={
|
||||
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
|
||||
<div
|
||||
|
@ -46,7 +51,6 @@ const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => {
|
|||
</div>
|
||||
}
|
||||
displayTitle={false}
|
||||
openGraph={getOpenGraph(langui)}
|
||||
displayLanguageSwitcher
|
||||
/>
|
||||
);
|
||||
|
@ -59,4 +63,21 @@ export default Home;
|
|||
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const getStaticProps = getPostStaticProps("home");
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const { format } = getFormat(context.locale);
|
||||
const post = await sdk.getPost({
|
||||
slug: "home",
|
||||
language_code: context.locale ?? "en",
|
||||
});
|
||||
if (post.posts?.data && post.posts.data.length > 0) {
|
||||
const props: PostStaticProps = {
|
||||
post: post.posts.data[0]?.attributes as PostWithTranslations,
|
||||
openGraph: getOpenGraph(format),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
return { notFound: true };
|
||||
};
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
prettyDate,
|
||||
prettyInlineTitle,
|
||||
prettyItemSubType,
|
||||
prettyItemType,
|
||||
prettyPrice,
|
||||
prettySlug,
|
||||
prettyURL,
|
||||
|
@ -49,11 +48,12 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { getDescription } from "helpers/description";
|
||||
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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">
|
||||
|
@ -563,7 +576,7 @@ export default LibrarySlug;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const item = await sdk.getLibraryItem({
|
||||
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||
language_code: context.locale ?? "en",
|
||||
|
@ -576,14 +589,14 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const description = getDescription(
|
||||
item.libraryItems.data[0].attributes.descriptions?.[0]?.description,
|
||||
{
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[format("category", { count: Infinity })]: filterHasAttributes(
|
||||
item.libraryItems.data[0].attributes.categories?.data,
|
||||
["attributes.short"]
|
||||
).map((category) => category.attributes.short),
|
||||
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes.metadata?.[0]
|
||||
[format("type", { count: Infinity })]: item.libraryItems.data[0].attributes.metadata?.[0]
|
||||
? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])]
|
||||
: [],
|
||||
[langui.release_date ?? "Release date"]: [
|
||||
[format("release_date")]: [
|
||||
item.libraryItems.data[0].attributes.release_date
|
||||
? prettyDate(item.libraryItems.data[0].attributes.release_date, context.locale)
|
||||
: undefined,
|
||||
|
@ -594,7 +607,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const props: Props = {
|
||||
item: item.libraryItems.data[0].attributes,
|
||||
itemId: item.libraryItems.data[0].id,
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail?.data?.attributes),
|
||||
openGraph: getOpenGraph(format, title, description, thumbnail?.data?.attributes),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
@ -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,10 +11,9 @@ 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";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
|
||||
import { Img } from "components/Img";
|
||||
import { getAssetFilename, ImageQuality } from "helpers/img";
|
||||
|
@ -40,6 +39,8 @@ 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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
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();
|
||||
|
@ -576,7 +577,7 @@ export default LibrarySlug;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const item = await sdk.getLibraryItemScans({
|
||||
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||
language_code: context.locale ?? "en",
|
||||
|
@ -657,7 +658,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
itemSlug: item.libraryItems.data[0].attributes.slug,
|
||||
pageRatio: `${pages[0]?.width ?? 21} / ${pages[0]?.height ?? 29.7}`,
|
||||
openGraph: getOpenGraph(
|
||||
langui,
|
||||
format,
|
||||
item.libraryItems.data[0].attributes.title,
|
||||
undefined,
|
||||
item.libraryItems.data[0].attributes.thumbnail?.data?.attributes
|
||||
|
@ -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>
|
||||
|
|
|
@ -23,10 +23,7 @@ import {
|
|||
} from "helpers/asserts";
|
||||
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 +33,8 @@ 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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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);
|
||||
|
@ -455,9 +454,9 @@ export default Library;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.library ?? "Library"),
|
||||
openGraph: getOpenGraph(format, format("library")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
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";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props extends AppLayoutRequired {}
|
||||
const Merch = (props: Props): JSX.Element => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={
|
||||
<SubPanel>
|
||||
<PanelHeader icon="store" title={langui.merch} description={langui.merch_description} />
|
||||
</SubPanel>
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default Merch;
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ NEXT DATA FETCHING ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.merch ?? "Merch"),
|
||||
};
|
||||
return {
|
||||
props: 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
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { Terminal } from "components/Cli/Terminal";
|
||||
import { atoms } from "contexts/atoms";
|
||||
|
@ -30,6 +29,8 @@ 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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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);
|
||||
|
@ -220,9 +225,9 @@ export default News;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.news ?? "News"),
|
||||
openGraph: getOpenGraph(format, format("news")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -19,11 +19,12 @@ import { getOpenGraph } from "helpers/openGraph";
|
|||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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",
|
||||
|
@ -228,7 +231,7 @@ export default WikiPage;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const slug =
|
||||
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||
const page = await sdk.getWikiPage({
|
||||
|
@ -239,12 +242,12 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const { title, description } = (() => {
|
||||
const chipsGroups = {
|
||||
[langui.tags ?? "Tags"]: filterHasAttributes(page.wikiPages.data[0].attributes.tags?.data, [
|
||||
[format("tags")]: filterHasAttributes(page.wikiPages.data[0].attributes.tags?.data, [
|
||||
"attributes",
|
||||
] as const).map(
|
||||
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
),
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
[format("category", { count: Infinity })]: filterHasAttributes(
|
||||
page.wikiPages.data[0].attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
|
@ -274,7 +277,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const props: Props = {
|
||||
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
||||
openGraph: getOpenGraph(format, title, description, thumbnail),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
|
@ -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";
|
||||
|
@ -26,9 +25,8 @@ import { TranslatedProps } from "types/TranslatedProps";
|
|||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -119,7 +117,7 @@ export default Chronology;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const chronologyItems = await sdk.getChronologyItems();
|
||||
const chronologyEras = await sdk.getEras();
|
||||
if (!chronologyItems.chronologyItems || !chronologyEras.chronologyEras) return { notFound: true };
|
||||
|
@ -127,7 +125,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const props: Props = {
|
||||
chronologyItems: chronologyItems.chronologyItems.data,
|
||||
chronologyEras: chronologyEras.chronologyEras.data,
|
||||
openGraph: getOpenGraph(langui, langui.chronology ?? "Chronology"),
|
||||
openGraph: getOpenGraph(format, format("chronology")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
@ -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>
|
||||
|
|
|
@ -17,14 +17,13 @@ import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/as
|
|||
import { prettySlug } from "helpers/formatters";
|
||||
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";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -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>
|
||||
);
|
||||
|
||||
|
@ -212,9 +211,9 @@ export default Wiki;
|
|||
*/
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
const langui = getLangui(context.locale);
|
||||
const { format } = getFormat(context.locale);
|
||||
const props: Props = {
|
||||
openGraph: getOpenGraph(langui, langui.wiki ?? "Wiki"),
|
||||
openGraph: getOpenGraph(format, format("wiki")),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
Loading…
Reference in New Issue