Amon's Blog

猛猛如玉

When we launch a product, we usually make a video to show our product’s functions and features. We also do this for our GitHub repository now.

However, how can we embed a video into a GitHub README?

In the past, we tried to add a video file to the repository, but the video file showed as a URL given the limitations of the platform. We also tried using markdown or HTML to embed a video, but it did not work at all.

Good news: Embedding a local video into a GitHub README is very easy now!

Just follow these steps:

1. Edit the README file on the GitHub online repository

The edit page url likes https://github.com/xumeng/ai-careers/edit/master/README.md

embed a video into github readme file

2. Drag the local video file to the edit panel

GitHub will uploading the file automatically, then it will showing a video asset url likes https://github.com/xumeng/ai-careers/assets/2187660/7362e4b8-6318-4cfc-af63-b8921455e434

drag the video file into the github readme file

3. Preview and commit the changes, We’re done!

generates job description by 职生机jobotai

GitHub README Demo: https://github.com/xumeng/ai-careers

References

https://stackoverflow.com/a/78521560/3090339

https://amonxu.medium.com/how-to-embed-a-video-into-github-readme-so-easy-c298ca92d537

Introduction:

This post introduces the implementation of i18n internationalization multi-language feature in Next.js 14 (based on App Router), and takes into consideration actual scenarios to optimize and perfect the feature step-by-step. By reading this post, you will immediately get how to implement i18n in Next.js.

hero

Preface

In an era where the Internet world is becoming increasingly flattening, the importance of multi-language products is growing. Fortunately, Next.js allows us to quickly support multi-language with simple configurations and code. However, when we search for how Next.js supports multi-languages on the Internet, we might find a variety of implementations, jumbled information, and excessively clever solutions. Then we become confused and start to question: What’s the issue here?

Today, let’s implement multi-language from 0 to 1 in Next.js and unravel the mystery of multi-language.

We can refer to the i18n introduction in the Next.js official documentation here: https://nextjs.org/docs/app/building-your-application/routing/internationalization, which is quite clear and detailed. This article will be based on this documentation.

Before we begin, let’s take a look at the final running effect: https://next-i18n-demo-two.vercel.app/

Ready to work

First, We create a Next.js app,

1
npx create-next-app@latest

Plese select App Router, I am using TypeScript here.

1
2
3
4
5
6
7
8
❯ npx create-next-app@latest
✔ What is your project named? … `next-i18n-demo`
✔ Would you like to use TypeScript? … No / `Yes`
✔ Would you like to use ESLint? … No / `Yes`
✔ Would you like to use Tailwind CSS? … No / `Yes
✔ Would you like to use `src/` directory? … No / `Yes`
✔ Would you like to use App Router? (recommended) … No / `Yes`
✔ Would you like to customize the default import alias (@/*)? … `No` / Yes

Run locally,

1
npm run dev

Open http://localhost:3000 and you will see that it is running okay.

Internationalization introduction

Before we start it, let’s briefly introduce about internationalization. internationalization, aka i18n, this means supporting multiple languages, cultures, and customs in products, mainly including language, time, currency symbols, etc. This article will focus only on the language part.

In terms of internationalization, a common approach is for a website to default to a certain language’s official site (usually English), and to support the selection of language or region, allowing for a switch to different language versions of the site.

Specifically, some websites use a language abbreviation as a prefix, such as en.wikipedia.org, zh.wikipedia.org; some use it as a path suffix, such as aws.amazon.com/cn, aws.amazon.com/jp, and others distinguish based on the country or region domain name, such as apple.cn, apple.jp.

Among these, en, zh, cn, jp, etc., are language codes, which can vary slightly in different versions. You can refer to the reference materials at the end of the article for specifics.

In this article’s particular case, the ISO_3166 codes en and zh will be used to represent English and Chinese respectively.

Begin to configure Multi-Languages

The original file structure of the project was:

1
2
3
4
5
6
7
8
9
10
11
12
├── package.json
├── public
│   ├── next.svg
│   └── vercel.svg
├── src
│   └── app
│   ├── favicon.ico
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── tailwind.config.ts
└── tsconfig.json

We create a new folder named [lang] in the app directory, then move laytout.tsx and page.tsx from the app directory to [locales].

The file structure after moving is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── package.json
├── postcss.config.mjs
├── public
│   ├── next.svg
│   └── vercel.svg
├── src
│   └── app
│   ├── [lang]
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── favicon.ico
│   └── globals.css
├── tailwind.config.ts
└── tsconfig.json

Tips:

Please modify the reference position of globals.css in layout.tsx.

Next, we define json resource files for different languages, which you can put in your preferred file directory. I put it in public/dictionaries. The file format is as follows:

en.json

1
2
3
4
5
6
7
8
9
10
{
"page": {
"title": "Next.js i18n Demo",
"desc": "How to implement i18n with Next.js (based on App Router)"
},
"home": {
"title": "Hello, Next.js i18n",
"desc": "This is a demo of Next.js i18n"
}
}

zh.json

1
2
3
4
5
6
7
8
9
10
{
"page": {
"title": "Next.js i18n 示例",
"desc": "搞懂 Next.js 实现 i18n 国际化多语言(基于App Router)"
},
"home": {
"title": "你好, Next.js i18n",
"desc": "这是一个 Next.js i18n 示例"
}
}

Then, we create a file to load the multi-language resource files and get the corresponding language text.

Add dictionaries.js in the app/[lang] directory. Make sure the file directory and file name are correct and match.

1
2
3
4
5
6
7
8
import 'server-only'

const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
zh: () => import('./dictionaries/zh.json').then((module) => module.default),
}

export const getDictionary = async (locale) => dictionaries[locale]()

Using Multi-Languages

We use the multi-language feature on the pages.tsx page.

First, add the lang parameter for the function, add async for the function,

1
2
3
export default async function Home({ params: { lang } }: { params: { lang: string } }) {
...
}

Use it on the page, add the multi-language fuction call,

1
const t = await getDictionary(lang);

For convenience, I clean up the default code on page.tsx and only retain the text display.

1
2
3
4
5
6
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
{t.home.title}
</p>
{t.home.desc}
</main>

Restart the program or wait for the program to hot update successfully, open different language pages http://localhost:3000/en http://localhost:3000/zh to check the effect.

Setting the Default Language

It looks pretty good, but careful friends will find that opening http://localhost:3000 will result in a 404 error. To solve this problem, we need to set a default language when no language is selected.

For this, we can create a middleware.ts in the src directory, and then copy the code from the documentation.

The core logic is simple:

Check whether there is a certain language identifier in URL’s pathname. If so, return directly. Otherwise, get the appropriate language and redirect the URL to /${locale}${pathname}

The focus is on the getLocale function. We need to specify the suitable language. For now, let’s deal with this simply: use the default defaultLocale = "en" .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { NextRequest, NextResponse } from "next/server";

let locales = ["en", "zh"];
let defaultLocale = "en";

// Get the preferred locale, similar to the above or using a library
function getLocale(request: NextRequest) {
return defaultLocale;
}

export function middleware(request: NextRequest) {
// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl;
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);

if (pathnameHasLocale) return;

// Redirect if there is no locale
const locale = getLocale(request);
request.nextUrl.pathname = `/${locale}${pathname}`;
// e.g. incoming request is /products
// The new URL is now /en-US/products
return NextResponse.redirect(request.nextUrl);
}

export const config = {
matcher: [
// Skip all internal paths (_next)
"/((?!_next).*)",
// Optional: only run on root (/) URL
// '/'
],
};

After the program updates, we open http://localhost:3000/ and see that it will automatically redirect to the default language page.

Optimization of Getting the Default Language

In the previous step, while get the default language, we treated it simply as defaultLocale = "en". A more graceful way is: Set the default language based on the user’s system or browser language.

We can achieve this by getting the Accept-Language field from the browser’s HTTP headers. The data format is approximately as follows:

1
2
3
4
English:
accept-language: en-US,en;q=0.5
Chinese:
accept-language: zh-CN,zh-Hans;q=0.9

We update middleware as follows:

  1. Get the Accept-Language from the HTTP headers. If it’s empty, then return the default language.
  2. Parse the language list in Accept-Language and match to get the corresponding language based on the configured language list. (If there is no match, return the default language)

Install dependencies @formatjs/intl-localematcher, negotiator, @types/negotiator, and implement the following logic:

1
2
3
4
5
6
7
function getLocale(request: NextRequest) {
const acceptLang = request.headers.get("Accept-Language");
if (!acceptLang) return defaultLocale;
const headers = { "accept-language": acceptLang };
const languages = new Negotiator({ headers }).languages();
return match(languages, locales, defaultLocale);
}

By changing the system language, open http://localhost:3000 and it will automatically redirect to the page with the same system language. Test successfully.

Other Handling of Multi-Language

Storing the Language

Going a step further, we can store the user’s web page language in the cookies and use it on the next visit:

1
2
3
4
5
6
7
// Get Cookie 
if (request.cookies.has(cookieName)) {
return request.cookies.get(cookieName)!.value;
}

// Set Cookie
response.cookies.set(cookieName, locale);

Web Metadata(Page Title/Descriptions..)

When using i18n in web page metadata, add the following code to page.tsx:

1
2
3
4
5
6
7
export async function generateMetadata({ params: { lang } } : { params: { lang: string } }) {
const t = await getDictionary(lang);
return {
title: t.page.title,
description: t.page.desc,
};
}

SSG(Static Generation)

When handling i18n in SSG, the code in layout.tsx is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface LangParams {
lang: string;
}

export async function generateStaticParams() {
return [{ lang: "en" }, { lang: "zh" }];
}

export default function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: LangParams;
}>) {
return (
<html lang={params.lang}>
<body className={inter.className}>{children}</body>
</html>
);
}

Language Switch(Language Switcher or Links)

You can add a language swicher (like a drop-down menu) or some links.

For example,

1
2
3
4
5
<div className="space-x-2">
<Link href="/en">English</Link>
<span>|</span>
<Link href="/zh">Chinese</Link>
</div>

End

Through the learning of the above steps, we initially familiarize and practice using multi-language in Next.js. A journey of thousand miles begins with a single step. The work of i18n is not limited to these, and of course, other areas need improvement which I’d leave to you, the reader.

Finally, here is the complete code of middleware.ts :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import Negotiator from "negotiator";
import { match } from "@formatjs/intl-localematcher";
import { NextRequest, NextResponse } from "next/server";

const locales = ["en", "zh"];
const defaultLocale = "zh";
const cookieName = "i18nlang";

// Get the preferred locale, similar to the above or using a library
function getLocale(request: NextRequest): string {
// Get locale from cookie
if (request.cookies.has(cookieName))
return request.cookies.get(cookieName)!.value;
// Get accept language from HTTP headers
const acceptLang = request.headers.get("Accept-Language");
if (!acceptLang) return defaultLocale;
// Get match locale
const headers = { "accept-language": acceptLang };
const languages = new Negotiator({ headers }).languages();
return match(languages, locales, defaultLocale);
}

export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/_next")) return NextResponse.next();

// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl;
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);

if (pathnameHasLocale) return;

// Redirect if there is no locale
const locale = getLocale(request);
request.nextUrl.pathname = `/${locale}${pathname}`;
// e.g. incoming request is /products
// The new URL is now /en-US/products
const response = NextResponse.redirect(request.nextUrl);
// Set locale to cookie
response.cookies.set(cookieName, locale);
return response;
}

export const config = {
matcher: [
// Skip all internal paths (_next)
"/((?!_next).*)",
// Optional: only run on root (/) URL
// '/'
],
};

You can get the full code from https://github.com/xumeng/next-i18n-demo .

The finally running demo: https://next-i18n-demo-two.vercel.app/


Reference:

https://nextjs.org/docs/app/building-your-application/routing/internationalization

https://en.wikipedia.org/wiki/ISO_3166

https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes

https://en.wikipedia.org/wiki/IETF_language_tag

https://www.alchemysoftware.com/livedocs/ezscript/Topics/Catalyst/Language.htm


My personal server uses CentOS 7.9, and there are often strange errors when deploying some AI applications.
For example, recently, an error was reported when deploying an application:

1
/lib64/libstdc++.so.6: version GLIBCXX_3.4.xx not found

Online search for solutions, there are different opinions, either reinstall gcc, or recompile and install libstdc++,export LD_LIBRARY_PATH and so on.
Several attempts have failed, and there is no way to upgrade the os version. Finally, a suitable solution is found, and it’s worked for me.

  1. Find and display all packages that provide libstdc++.so.6 as a library file through yum
1
sudo yum provides libstdc++.so.6
  1. Download new version libstdc.so.

NOTICE: Since I need version 3.4.22+, so I can just update it to 3.4.26. Other versions are the same.

1
2
3
cd /usr/local/lib64
sudo wget http://www.vuln.cn/wp-content/uploads/2019/08/libstdc.so_.6.0.26.zip
unzip libstdc.so_.6.0.26.zip
  1. Copy libstdc++.so.6.0.26 to /usr/lib64
1
2
cp libstdc++.so.6.0.26 /usr/lib64
cd /usr/lib64
  1. Check the soft link version of libstdc++.so.6,
1
ls -l | grep libstdc++

It may shows like this:

1
libstdc++.so.6 ->libstdc++.so.6.0.19
  1. Remove /usr/lib64 original link libstdc++.so.6, you can backup it before remove.
1
sudo rm libstdc++.so.6

then, relink it.

1
sudo ln -s libstdc++.so.6.0.26 libstdc++.so.6
  1. OK, check the newest link
1
strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX

It may shows like this:

1
2
3
4
5
GLIBCXX_3.4
...
GLIBCXX_3.4.25
GLIBCXX_3.4.26
GLIBCXX_DEBUG_MESSAGE_LENGTH

Well Done!


In my Hexo blog, when I include special symbols inside the title in posts, it reports this error:

1
2
3
ERROR Process failed: _posts/en/XXX.md
YAMLException: incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line at line 1, column 54:
... for Backend System Refactoring\: How to do backend system refac ...

Solution:
Include the title in single quotes, like:

1
2
3
title: 'How to read a book'
lang: en
date: 2023-01-01

coverimg

Introduction

As the company’s business experiences explosive growth, both the scale of requirements and the user base are rapidly expanding. This presents challenges to the system in terms of the three high (high performance, high concurrency, high availability), scalability, and maintainability. The old system, due to various limitations in its early design (such as the expertise of early participants, the foresight of architectural design, impatience of management, etc.), gradually becomes inadequate to meet current and future demands, exposing various issues. Developers find themselves dragging an old, worn-out car on the highway, which is a daunting task. In simpler terms, the codebase of the old system has become too problematic to fix, leading to a situation where developers either get buried in its issues or abandon the project altogether.

At this point, a common question arises: should we continue trying to patch the issues, or should we choose to refactor? Patching is simply not feasible, not in this lifetime. Refactoring, on the other hand, requires the courage of a true hero because it’s a complex and time-consuming task. Moreover, it can impact ongoing business development or even bring it to a standstill. Often, product managers and executives are not supportive because they only care about one thing: when will the next feature be ready? Everything else is your development team’s problem.

If you choose the path of refactoring, you must be prepared to see it through, no matter what. How can you ensure a successful refactoring from the get-go? Based on common practices in internet projects and my personal experience in refactoring projects, here is an outline of the common steps for refactoring systems of various sizes:

Step 0: Convincing Stakeholders

Refactoring is not just the responsibility of the development team; it’s a collective effort involving the entire project team. Refactoring can improve the system’s performance, availability, and scalability, as well as optimize and streamline business processes to meet new demands. It requires a significant investment of resources and must have the support of stakeholders. Typically, this requires explaining the benefits and drawbacks of refactoring, as well as the critical issues that would arise if refactoring is not done. Once you have their support, the refactoring work can officially begin.

Participants: Technical Leader

Step 1: Establish Clear Refactoring Goals

Refactoring is a long-term endeavor; it’s not something that can be completed in one or two iterations, or even within a few months. It requires a substantial investment of manpower, resources, time, and effort. So, what are our goals in this prolonged battle? Are we aiming to meet the system’s high-performance requirements through a more efficient architecture? Or do we want to enhance code quality through refactoring? Perhaps we aim to introduce new technologies and frameworks to upgrade the entire system or optimize business processes to address previously unmet requirements. Once you have clear goals, you can work purposefully.

Participants: Technical Leader, Architect

Step 2: Define the Scope of Refactoring and Make Predictions

Refactoring typically falls into several levels:

  • Platform-level refactoring: Refactoring the entire platform, such as Alibaba transitioning from the LAMP stack to the Java platform.
  • System-level refactoring: Refactoring specific business systems, such as introducing microservices or SOA architecture to break down monolithic applications.
  • Architecture-level refactoring: Improving the existing architecture through adjustments and redesign, addressing architectural shortcomings, like decoupling business logic through layered design or introducing caching for improved concurrency.
  • Business-level refactoring: Addressing specific business requirements that cannot be met due to the limitations of the current system, often involving the refactoring of business processes or database structures.
  • Module/code-level refactoring: The most common form of refactoring, typically involving the use of design patterns, encapsulation, and code optimization to improve code structure and performance.

Determine the level of refactoring required, the overall scope, and the technology stack for refactoring. Then, conduct a scientific assessment and estimation of the refactoring work. This includes identifying the costs, required resources, and time commitments, as well as assessing whether ongoing business requirements can be accommodated during the refactoring process. Once these predictions are established, you can provide stakeholders with a clear understanding, especially when they ask when new requirements can be delivered.

Participants: Technical Leader, Architect, Developers

Step 3: Familiarize Yourself with the Old System and Document Business Processes

Refactoring is not about abandoning the old system; it’s about continuously working with it. Knowing your enemy is the key to victory. Refactoring not only requires a clear understanding of the new system’s goals and future, but also a deep familiarity with the old system, especially its pitfalls. At this stage, the participants in the refactoring project, especially those who worked on the old system, should document and organize information related to the old system’s business and technical details. This includes collecting documents such as design documents, technical documents, architecture diagrams, UML diagrams, and ER diagrams related to the system.

The following are common preparation tasks before refactoring the old system:

  • Gathering information and documentation related to the old system, including design documents, technical documents, architectural diagrams, UML diagrams, ER diagrams, and other graphical materials.
  • Mapping and documenting business lines and processes, outlining projects and business flows, and documenting them.
  • Reviewing key code and database designs in the old system.

Any issues or uncertainties should be addressed promptly through communication with relevant personnel from the business side, ensuring that problems are resolved early in the process.

Participants: Technical Leader, Architect, Developers

Step 4: Database Refactoring

If the refactoring involves changes to the database, database refactoring is typically the first step. Many refactoring initiatives are triggered by issues related to the database. During database refactoring, the deficiencies and obstacles in the old system’s database design are addressed. This may involve redesigning tables using normalization or denormalization techniques, considering sharding or partitioning strategies, and more.

Participants: DBA, Architect

Step 5: Backend System Refactoring

Before starting the backend system refactoring, it’s essential to have design and technical documentation in place, as mentioned earlier. Once these documents are finalized through discussions and planning, the architect can proceed with system architecture design, and backend developers can begin coding. This phase is often the most time-consuming and critical part of the refactoring process. The quality of the backend architecture directly affects the success of the refactoring, the quality of the business code, and the overall refactoring quality.

Due to the extended timeline of this phase and the fact that its results may not be immediately visible, Agile development methodologies are often used. This allows for iterative development, ensuring effective planning and continuous progress. The advantages of using iterations include:

  1. Effective planning and quantification of the entire refactoring process.
  2. Visible achievements at each stage, preventing the team from getting stuck in a long refactoring process.
  3. The ability to test or observe refactored parts promptly during iterations, allowing continuous learning and improvement.

During backend system refactoring, it’s essential to have clear, quantifiable goals and standards. For example, defining the QPS (Queries Per Second) supported by various systems and business modules, the expected response times for interfaces, etc. This enables the team to focus on achieving these goals during refactoring.

Regular code reviews should also be conducted throughout the refactoring process to identify and address issues with the refactoring itself and the quality of the code. This helps prevent the introduction of poor designs or subpar code that could harm the entire system.

Participants: Technical Leader, Architect, Developers

Step 6: Data Migration and Verification

If database refactoring is part of the project, data migration becomes a crucial step. It generally involves two types of migration: full migration and incremental migration. Full migration transfers all data from the old system to the new one in one go, while incremental migration handles data created in the old system after full migration until the old system is retired. These migrations are typically scripted or programmed to avoid manual errors.

After migration, it’s essential to compare the data between the old and new systems. This comparison can also be automated through scripts or programs to identify discrepancies and perform any necessary adjustments or investigations.

Participants: DBA, Developers

Step 7: System Validation, Integration, and Testing

As the backend system refactoring progresses, scripts and programs should be developed to validate the business interfaces between the old and new systems. This ensures that issues in the refactoring process are detected promptly, and, if necessary, architectural and database adjustments can be made. Additionally, increasing unit test coverage during refactoring is highly beneficial.

Once the dependencies between systems and modules are resolved, integration testing can begin. Comprehensive testing, including functional testing, stability testing, performance testing, local testing, and simulating production environments, should be performed. Any issues identified during testing should be addressed, verified, and fixed to meet the standards required for a smooth release.

Participants: Architect, Developers, Testers

Step 8: Gradual Deployment and Monitoring

When the backend system refactoring reaches a certain level of stability, it’s time to initiate gradual deployment. During this phase, only a portion of the traffic is directed to the new system. This allows for real-time tracking and analysis of logs and monitoring alarms. Any issues or anomalies can be addressed promptly. As confidence in the new system’s stability grows, the scope and volume of the deployment can be gradually increased. Continuous monitoring of logs and alarms should be maintained throughout this phase.

Participants: DevOps Team, Testers, Developers

Step 9: System Transition

When it comes to transitioning to the new system, it’s crucial to have a well-defined transition plan in place. This plan should include detailed processes, workflows, and contingency plans, including rollback procedures in case unexpected issues arise. This step ensures that the transition is smooth and minimizes disruption to the business.

Participants: DevOps Team, Testers

Conclusion

After completing the above steps, the system has undergone successful refactoring. However, it’s essential to understand that refactoring is a substantial undertaking, and even after the process, the system may not be flawless. Refactoring is not the endpoint but rather a new beginning.


There are multiple Git libraries are used on my machine, such as GitHub/Company Private Git repo, etc., the Git tool mainly uses Terminal and GitKraken, and occasionally strange issues will arise. For example, the following error occurred when deploying the Hexo Blog a few days ago:

1
2
3
4
5
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

The usual solution is to regenerate the SSH Key of GitHub.

But there’s no issue with my local configuration, I can push code normally in Terminal and GitKraken.

Guessing that there might be some hitch in the process, I used:

1
ssh -T git@github.com

And saw:

1
Hi {username}! You've successfully authenticated, but GitHub does not provide shell access.

Then redeployed and pushed, It’s worked!

Reference:

https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey

cover
As we know,after iOS 10,Apple launched Notification Service Extension,Looking at the doc,the description of UNNotificationServiceExtension is An object that modifies the content of a remote notification before it's delivered to the user.,that is, in a Before the remote notification is displayed to the user, the notification can be modified throughUNNotificationServiceExtension.

Stop talking nonsense and get down to business.

一、How to monitor received push notifications

  1. Create a new Target on the original Project, Select Create
    image-20190304210307444

  2. Add a new field mutable-content": "1" to the content pushed by the server,,at the same level as alert/badge/sound。 For example, when testing push in the push platform, the settings are as follows:image-20190304212029192

  3. Run the main Target of the Project, then run Notification Service Extension,and select the main Target

  4. Send test push,then you can see it execute and enter - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler

二、How to statistics

  1. If you are connecting a 3rd push platform, you can check whether it supports. For example: Jiguang API: Notification Service Extension

  2. If you implement it yourself, there are two options:

    1. Write network requests in the notification extension project and send the push arrival data to the backend server
    2. Save the push arrival data to the local App through App Group, and then process it in the main Target

三、How to use App Groud to share data

  1. Login to https://developer.apple.com ,Create App Group

  2. Configure in project,target - Capabilites - App groups

  3. Use in code

    1. Use in NSUserDefaults

      1
      2
      3
      4
      5
      6
      NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.company.appGroupName"];
      // write data
      [userDefaults setValue:@"value" forKey:@"key"];

      //read data
      NSLog(@"%@", [userDefaults valueForKey:@"key"]);
    2. Use in NSFileManager

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      // write data
      NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.domain.groupName"];
      NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"fileName"];

      NSString *text = @"Go amonxu.com";
      if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
      [text writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
      } else {
      NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingURL:fileURL error:nil];
      [fileHandle seekToEndOfFile];
      [fileHandle writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
      [fileHandle closeFile];
      }


      // read data
      NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.domain.groupName"];
      NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"fileName"];
      NSString *fileContent = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
0%