next.js创建新项目
This article was originally posted here.
本文最初发布在这里 。
If you missed the first part of this article, I suggest you take a look at it before continuing reading this one. In order not to make the article too long, I chose to split it into two parts. In the previous part we saw how to translate the words on screen. Now, we will deal with the creation and listing of content for each language. Without further ado, here we go!
如果您错过了本文的第一部分,建议您先阅读一下,然后再继续阅读本文。 为了不使文章太长,我选择将其分为两部分。 在上一部分中,我们看到了如何在屏幕上翻译单词。 现在,我们将处理每种语言的内容的创建和列出。 事不宜迟,我们开始了!
每种语言的降价内容 (Markdown content for each language)
The file structure follows the example below:
文件结构遵循以下示例:
---
lang: pt
title: "Artigo em português"
slug: artigo
date: "2020-07-12"
category: post
description: "Lorem ipsum dolor sit amet consectetuer adispiscing elit"
---## LoremLorem ipsum dolor sit amet consectetuer adispiscing elit.
If you don’t know Markdown, this header between ---
is called "frontmatter". With it, we pass information that will be used for the listing and display of the content. Below is a brief description of what each field does:
如果您不知道Markdown,则---
之间的此标头称为“ frontmatter”。 有了它,我们传递了将用于内容列表和显示的信息。 以下是每个字段的简要说明:
lang: ISO of the language used in the content.
lang :内容中使用的语言的ISO。
title: title of the article.
title :文章标题。
date: date of the article, in YYYY-MM-DD format. Note that it is enclosed in quotation marks, otherwise Next.js throws an error.
date :文章的日期,格式为YYYY-MM-DD。 请注意,它用引号引起来,否则Next.js会引发错误。
description: summary of the article on the article listing page.
description :文章列表页面上文章的摘要。
category: category of the article.
category :文章的类别。
You have freedom to create your own fields in this header, like tags and stuff. For the example cited here, this is enough.
您可以在此标头中自由创建自己的字段,例如标签和内容。 对于此处引用的示例,这已足够。
读取Markdown文件的库 (Library to read Markdown files)
As you can already know, Markdown files are the basis of our content. To read these files and convert them to HTML, three packages need to be installed: Remark and Remark-HTML and Gray Matter. The latter reads the * .md
file frontmatter.
如您所知,Markdown文件是我们内容的基础。 要读取这些文件并将其转换为HTML,需要安装三个软件包:Remark和Remark-HTML和Gray Matter。 后者读取* .md
文件的前题。
In order to install it:
为了安装它:
yarn add remark remark-html gray-matternpm install --save remark remark-html gray-matter
This part was easy, however, creating the post loop is not that simple. First I followed the tutorial that the folks at Next.js did, but I had to make some adjustments to add the possibility of saving the files in different folders, by language. Below is the commented code.
这部分很简单,但是创建post循环并不是那么简单。 首先,我遵循了Next.js上的教程,但是我不得不进行一些调整,以增加按语言将文件保存在不同文件夹中的可能性。 下面是注释的代码。
import fs from "fs"
import path from "path"
import matter, { GrayMatterFile } from "gray-matter"
import remark from "remark"
import html from "remark-html"// Directory used to read markdown files
const postsDirectory = path.resolve(process.cwd(), "posts")// Returns a list of files in the directories and
// subdirectories in the formal ['en/filename.md']
function getAllPostFileNames(directoryPath, filesList = []) {
const files = fs.readdirSync(directoryPath) files.forEach((file) => {
if (fs.statSync(`${directoryPath}/${file}`).isDirectory()) {
filesList = getAllPostFileNames(`${directoryPath}/${file}`, filesList)
} else {
filesList.push(path.join(path.basename(directoryPath), "/", file))
}
}) // Filter to include only * .md files
// If you don't use this, even .DS_Stores are included
const filteredList = filesList.filter((file) => file.includes(".md"))
return filteredList
}// Collects information from files and sorts them by date
export function getSortedPostData() {
// Get the list of * .md files in the posts directory
const fileNames = getAllPostFileNames(postsDirectory) // Uses gray-matter to collect information from the file
const allPostsData = fileNames.map((fileName) => {
const id = fileName.split("/")[1].replace(/\.md$/, "")
const fullPath = path.join(postsDirectory, fileName)
const fileContents = fs.readFileSync(fullPath, "utf-8")
const frontMatter: GrayMatterFile = matter(fileContents) return {
id,
...(frontMatter.data as {
lang: string
date: string
category: string
}),
}
}) // Sorts collected information by date
return allPostsData.sort((a, b) => {
if (a.date < b.date) {
return 1
} else {
return -1
}
})
}// Separates the file name and language
export function getAllPostIds() {
// Get the list of * .md files in the posts directory
const fileNames = getAllPostFileNames(postsDirectory) // Splits the "en" and "filename" parts of ['en/filename.md']
// and return them as parameters for later use in Next
return fileNames.map((fileName) => ({
params: {
id: fileName.split("/")[1].replace(/\.md$/, ""),
lang: fileName.split("/")[0],
},
}))
}// Make the data available for the informed post.
export async function getPostData(id) {
const fullPath = path.join(postsDirectory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, "utf-8")
const frontMatter = matter(fileContents) const processedContent = await remark().use(html).process(frontMatter.content) const contentHtml = processedContent.toString() return {
id,
...(frontMatter.data as { date: string; title: string }),
contentHtml,
}
}
For those who have used Gatsby, this file is the equivalent of the gatsby-node.js
file. It makes file data available for viewing in Next.js.
对于使用Gatsby的用户,此文件与gatsby-node.js
文件等效。 它使文件数据可在Next.js中查看。
上市帖子 (Listing posts)
Next.js uses its own way of routing. Unlike Gatsby, where you define the routes of the listing pages in the gatsby-node.js
file, you use the folder structure itself.
Next.js使用其自己的路由方式。 与盖茨比不同,后者在gatsby-node.js
文件中定义列表页面的路由,而是使用文件夹结构本身。
To have a site.com/language/post/article
URL, simply create the directories following this structure, inside the /pages
folder that we already used to create the other pages.
要获得site.com/language/post/article
URL,只需在我们已经用来创建其他页面的/pages
文件夹内创建遵循此结构的目录即可。
If we just did something like suggested above, we would have the same result visually, but using React components instead of the .md
files. In the end we would have several *.tsx files and a folder for each language. This is not the best way approach, though.
如果我们只是像上面建议的那样做,我们将在视觉上得到相同的结果,但是使用React组件而不是.md
文件。 最后,我们将为每个语言提供多个* .tsx文件和一个文件夹。 但是,这不是最佳方法。
It makes a lot more sense to leave the content files in Markdown and use something dynamic to read this content and generate the static pages. Next.js can use the folder and file names to express a dynamic part of the route, using square brackets.
将内容文件留在Markdown中并使用动态内容读取此内容并生成静态页面会更有意义。 Next.js可以使用文件夹和文件名使用方括号来表示路线的动态部分。
On the right, the way Next.js organizes dynamic routes
在右侧,Next.js组织动态路由的方式
Instead of making the structure on the left, we will use the leaner version on the right. In this example, the file for listing files is articles.tsx
. It is inside the /[lang]
folder which will tell Next.js that the variable "lang" will be used at the URL: site.com/[lang]/articles
. This [lang]
will be replaced by pt
oren
according to the language to be displayed. Here is the code for the file:
与其在左侧创建结构,不如在右侧使用精简版本。 在此示例中,用于列出文件的文件是articles.tsx
。 它位于/[lang]
文件夹中,该文件夹将告诉Next.js在URL: site.com/[lang]/articles
/[lang]
articles处使用变量“ lang”。 根据要显示的语言,此[lang]
将由pt
或en
代替。 这是文件的代码:
import { useState } from "react"
import { NextPage, GetStaticProps, GetStaticPaths } from "next"
import Link from "next/link"import Layout from "../../components/Layout"
// Import function that lists articles by date
import { getSortedPostData } from "../../lib/posts"
import useTranslation from "../../intl/useTranslation"interface Props {
locale: string
allPostsData: {
date: string
title: string
lang: string
description: string
id: any
}[]
}const Post: NextPage = ({ locale, allPostsData }) => {
const { t } = useTranslation() // Articles filtered by language
const postsData = allPostsData.filter((post) => post.lang === locale) // Pagination
const postsPerPage = 10
const numPages = Math.ceil(postsData.length / postsPerPage)
const [currentPage, setCurrentPage] = useState(1)
const pagedPosts = postsData.slice(
(currentPage - 1) * postsPerPage,
currentPage * postsPerPage
) // Date display options
const dateOptions = {
year: "numeric",
month: "long",
day: "numeric",
} return (
{t("articles")}
{/* List of articles */}
{pagedPosts.map((post) => (
{post.title}
{post.description && {post.description}
}
))} {/* Paging */}
{numPages > 1 && (
{Array.from({ length: numPages }, (_, i) => (
))}
)}
)
}// Captures the information needed for the static page
export const getStaticProps: GetStaticProps = async (ctx) => {
// All site articles
const allPostsData = getSortedPostData() // Returns the properties used in the main component: the page
return {
props: {
locale: ctx.params?.lang || "pt", // Captures the language of [lang] route
allPostsData,
},
}
}// Generates static files on export
export const getStaticPaths: GetStaticPaths = async () => {
// All supported languages must be listed in 'paths'.
// If not informed, the static page will not be generated.
return {
paths: [{ params: { lang: "en" } }, { params: { lang: "pt" } }],
fallback: false,
}
}export default Post
As the intention is to generate static files, I used the getStaticProps()
function to capture the information and getStaticPaths
to inform the system the path where the pages will be exported.
为了生成静态文件,我使用了getStaticProps()
函数来捕获信息,并使用getStaticPaths
来通知系统页面将导出到的路径。
发布页面 (Post page)
Another page with the special file name, to inform a dynamic route. This time the parameter will be the file id, which is captured by the getAllPostIds()
function of the lib/posts
file, so the name of this component will be[lang]/posts/[id].tsx
. Below, its contents:
带有特殊文件名的另一个页面,用于通知动态路由。 这次,参数将是文件ID,由lib/posts
文件的getAllPostIds()
函数捕获,因此此组件的名称将为[lang]/posts/[id].tsx
。 下面,其内容:
import { GetStaticProps, GetStaticPaths, NextPage } from "next"/* - getAllPostIds: Gets the file id, that is, the file name
markdown without the * .md extension
- getPostData: Collects information from a single article by the given id.
*/
import { getAllPostIds, getPostData } from "../../../lib/posts"
import Layout from "../../../components/Layout"interface Props {
locale: string
postData: {
lang: string
title: string
slug: string
date: string
category: string
contentHtml: string
}
}const Post: NextPage = ({ postData, locale }) => {
const { title, contentHtml } = postData return (
{title}
className="post-text"
dangerouslySetInnerHTML={
{ __html: contentHtml }}
/>
)
}// As in the list page, passes the captured information to the page properties
export const getStaticProps: GetStaticProps = async ({ params }) => {
// Collect data from the post "en/filename"
const postData = await getPostData(`/${params.lang}/${params.id}`) return {
props: {
locale: params?.lang || "pt", // Captures [lang] from URL
postData,
},
}
}// Use getAllPostIds to inform which pages to generate when exporting static files.
export const getStaticPaths: GetStaticPaths = async () => {
const paths = await getAllPostIds() return {
paths,
fallback: false,
}
}export default Post
This is enough for a simple blog page.
这对于一个简单的博客页面就足够了。
结语 (Wrapping it up)
To write these two articles, I used the reference I left below. It was the closest one to what I wanted to achieve. However, there are certain things that were not so useful to me, or caused unwanted complexity for the size of the project. Note that there is no need for external libraries for the translations, which is quite interesting. If you have any questions or suggestions leave a comment. I will be grad to get your feedback!
在撰写这两篇文章时,我使用了下面剩下的参考。 这是最接近我想要实现的目标。 但是,有些事情对我而言并没有那么有用,或者导致了项目规模的不必要的复杂性。 请注意,翻译不需要外部库,这很有趣。 如果您有任何问题或建议,请发表评论。 我将是研究生,以获取您的反馈!
Below, I left a link to this project repository on Github, in case you want to see the complete source code.
下面,我留下了指向Github上此项目存储库的链接,以防您想要查看完整的源代码。
链接 (Links)
Part 1 on Dev.to
Dev.to的第1部分
GitHub repo
GitHub回购
Site made with the repo code
使用回购代码制作的网站
翻译自: https://medium.com/swlh/making-a-multilingual-site-with-next-js-part-2-518c5c699d23
next.js创建新项目