diff --git a/.gitlab/issue_templates/default.md b/.gitlab/issue_templates/default.md
index f5823ea666..db5fc81666 100644
--- a/.gitlab/issue_templates/default.md
+++ b/.gitlab/issue_templates/default.md
@@ -90,6 +90,8 @@ By submitting this issue, you agree to follow our [Contribution Guidelines](http
 	However, we are currently so understaffed that it is virtually impossible to
 	respond to every single proposal. So, feel free to implement it if there is no response
 	for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
+
+	Many thanks for your involvement!
 -->
 
 
diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md
index a99265161b..eb12540da1 100644
--- a/.gitlab/issue_templates/feature.md
+++ b/.gitlab/issue_templates/feature.md
@@ -58,6 +58,8 @@ By submitting this issue, you agree to follow our [Contribution Guidelines](http
 	However, we are currently so understaffed that it is virtually impossible to
 	respond to every single proposal. So, feel free to implement it if there is no response
 	for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
+
+	Many thanks for your involvement!
 -->
 
 
diff --git a/.gitlab/issue_templates/refactor.md b/.gitlab/issue_templates/refactor.md
index e9f9ffe05f..33b0077403 100644
--- a/.gitlab/issue_templates/refactor.md
+++ b/.gitlab/issue_templates/refactor.md
@@ -58,6 +58,8 @@ By submitting this issue, you agree to follow our [Contribution Guidelines](http
 	However, we are currently so understaffed that it is virtually impossible to
 	respond to every single proposal. So, feel free to implement it if there is no response
 	for more than a week or there is a thumbs-up emoji reaction from the project maintainer(s).
+
+	Many thanks for your involvement!
 -->
 
 
diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md
index 6d09072a87..7a37db2c9e 100644
--- a/.gitlab/merge_request_templates/default.md
+++ b/.gitlab/merge_request_templates/default.md
@@ -6,9 +6,7 @@
 
 ## Contribution Guidelines
 By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
-- [ ] This closes issue #0000 (please substitute the number)
-- [ ] This is a minor bug fix or refactoring
-
+- [ ] This closes #0000 (please substitute the issue number or open a new one unless this is a minor fix/refactor)
 - [ ] I agree to follow this project's Contribution Guidelines
 - [ ] I have made sure to test this merge request
 - [ ] I have made sure to run `pnpm run format` before submitting this merge request
diff --git a/docs/api-change.md b/docs/api-change.md
index 7e09e29205..20f87e9f82 100644
--- a/docs/api-change.md
+++ b/docs/api-change.md
@@ -4,6 +4,7 @@ Breaking changes are indicated by the :warning: icon.
 
 ## Unreleased
 
+- Added `readCatLanguage` field to the response of `i` and request of `i/update` (optional).
 - The old Mastodon API has been replaced with a new implementation based on Iceshrimp’s.
   - :warning: The new API uses a new format to manage Mastodon sessions in the database, whereas old implementation uses Misskey sessions. All previous client app and token registrations will not work with the new API. All clients need to be re-registered and all users need to re-authenticate.
   - :warning: All IDs (of statuses/notes, notifications, users, etc.) will be using the alphanumerical format, aligning with the Firefish/Misskey API. The old numerical IDs will not work when queried against the new API.
diff --git a/docs/changelog.md b/docs/changelog.md
index 3beafdcf3f..7eb7f59eaa 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -7,8 +7,9 @@ Critical security updates are indicated by the :warning: icon.
 
 ## Unreleased
 
-- Fix bugs
 - Mastodon API implementation was ported from Iceshrimp, with added Firefish extensions including push notifications, post languages, schedule post support, and more. (#10880)
+- Add ability to disable the cat language conversion (nyaification)
+- Fix bugs
 
 ### Acknowledgement 
 
diff --git a/docs/downgrade.sql b/docs/downgrade.sql
index 86e5930969..a8a949de62 100644
--- a/docs/downgrade.sql
+++ b/docs/downgrade.sql
@@ -5,6 +5,7 @@ DELETE FROM "migrations" WHERE name IN (
     'SwSubscriptionAccessToken1709395223611',
     'UserProfileMentions1711075007936',
     'ClientCredentials1713108561474',
+    'TurnOffCatLanguage1720107645050',
     'RefactorScheduledPosts1716804636187',
     'RemoveEnumTypenameSuffix1716462794927',
     'CreateScheduledNote1714728200194',
@@ -52,6 +53,9 @@ ALTER TABLE "user_profile" DROP COLUMN "mentions";
 -- client-credential-support
 ALTER TABLE "access_token" ALTER COLUMN "userId" SET NOT NULL;
 
+-- turn-off-cat-language
+ALTER TABLE "user" DROP COLUMN "readCatLanguage";
+
 -- refactor-scheduled-post
 CREATE TABLE "scheduled_note" (
 	"id" character varying(32) NOT NULL PRIMARY KEY,
diff --git a/docs/notice-for-admins.md b/docs/notice-for-admins.md
index 83bdf39d8b..8a415d6c57 100644
--- a/docs/notice-for-admins.md
+++ b/docs/notice-for-admins.md
@@ -10,7 +10,15 @@ Please take a look at #10947.
 
 ### For all users
 
-This is not related to the recent changes, but we have added a new section called "[Maintain the server](https://firefish.dev/firefish/firefish/-/blob/develop/docs/install.md#maintain-the-server)" in the installation guide. We suggest that you take a look at it. (and we welcome your docs contributions!)
+This is not related to the recent changes, but we have added a new section called "[Maintain the server](https://firefish.dev/firefish/firefish/-/blob/develop/docs/install.md#maintain-the-server)" in the installation guide. We suggest that you take a look at it (and we welcome your docs contributions)!
+
+### For systemd/pm2 users
+
+[Node.js will release a new security fix on July 8th](<https://nodejs.org/en/blog/vulnerability/july-2024-security-releases>). It is highly recommended that you upgrade your Node.js version once it's released.
+
+### For Docker/Podman users
+
+[Node.js will release a new security fix on July 8th](<https://nodejs.org/en/blog/vulnerability/july-2024-security-releases>). Once it's released and the [docker.io/node](<https://hub.docker.com/_/node>) image is updated, we'll rebuild the OCI image based on the new `docker.io/node` image and reupload it as [`registry.firefish.dev/firefish/firefish:latest`](<https://firefish.dev/firefish/firefish/container_registry/1>).
 
 ### For systemd/pm2 users
 
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 68fc659499..47b69499fa 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1241,6 +1241,7 @@ showNoAltTextWarning: "Show a warning if you attempt to post files without a des
 showAddFileDescriptionAtFirstPost: "Automatically open a form to write a description
   when you attempt to post files without a description"
 addAlt4MeTag: "Automatically append #Alt4Me hashtag to your post if attached file has no description"
+turnOffCatLanguage: "Turn off cat language conversion"
 
 _emojiModPerm:
   unauthorized: "None"
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index 2e0d2584a8..3dbe6c31a0 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -1306,6 +1306,8 @@ export interface Services {
   outbound: Array<Outbound>
 }
 
+export declare function shouldNyaify(readerUserId: string): Promise<boolean>
+
 /** Prints the server hardware information as the server info log. */
 export declare function showServerInfo(): void
 
@@ -1429,6 +1431,7 @@ export interface User {
   emojiModPerm: UserEmojiModPerm
   isIndexable: boolean
   alsoKnownAs: Array<string> | null
+  readCatLanguage: boolean
 }
 
 export const USER_ACTIVE_THRESHOLD: number
@@ -1547,10 +1550,10 @@ export interface UserProfile {
   preventAiLearning: boolean
   isIndexable: boolean
   mutedPatterns: Array<string>
+  mentions: Json
   mutedInstances: Array<string>
   mutedWords: Array<string>
   lang: string | null
-  mentions: Json
 }
 
 export enum UserProfileFfvisibility {
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 363eb54f6f..6e0a162fc5 100644
--- a/packages/backend-rs/index.js
+++ b/packages/backend-rs/index.js
@@ -432,6 +432,7 @@ module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestati
 module.exports.safeForSql = nativeBinding.safeForSql
 module.exports.SECOND = nativeBinding.SECOND
 module.exports.sendPushNotification = nativeBinding.sendPushNotification
+module.exports.shouldNyaify = nativeBinding.shouldNyaify
 module.exports.showServerInfo = nativeBinding.showServerInfo
 module.exports.sqlLikeEscape = nativeBinding.sqlLikeEscape
 module.exports.sqlRegexEscape = nativeBinding.sqlRegexEscape
diff --git a/packages/backend-rs/src/database/cache.rs b/packages/backend-rs/src/database/cache.rs
index 2fe192cb1e..60abea56e8 100644
--- a/packages/backend-rs/src/database/cache.rs
+++ b/packages/backend-rs/src/database/cache.rs
@@ -9,6 +9,7 @@ pub enum Category {
     FetchUrl,
     Block,
     Follow,
+    CatLang,
     #[cfg(test)]
     Test,
 }
@@ -33,6 +34,7 @@ fn categorize(category: Category, key: &str) -> String {
         Category::FetchUrl => "fetchUrl",
         Category::Block => "blocking",
         Category::Follow => "following",
+        Category::CatLang => "catlang",
         #[cfg(test)]
         Category::Test => "usedOnlyForTesting",
     };
diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs
index c9f7cb9a60..37cdbee45a 100644
--- a/packages/backend-rs/src/misc/mod.rs
+++ b/packages/backend-rs/src/misc/mod.rs
@@ -15,4 +15,5 @@ pub mod nyaify;
 pub mod password;
 pub mod reaction;
 pub mod remove_old_attestation_challenges;
+pub mod should_nyaify;
 pub mod system_info;
diff --git a/packages/backend-rs/src/misc/should_nyaify.rs b/packages/backend-rs/src/misc/should_nyaify.rs
new file mode 100644
index 0000000000..bf4166f18d
--- /dev/null
+++ b/packages/backend-rs/src/misc/should_nyaify.rs
@@ -0,0 +1,45 @@
+//! Determine whether to enable the cat language conversion
+
+use crate::{
+    database::{cache, db_conn},
+    model::entity::user,
+};
+use sea_orm::{DbErr, EntityTrait, QuerySelect, SelectColumns};
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[doc = "database error"]
+    #[error(transparent)]
+    Db(#[from] DbErr),
+    #[doc = "cache error"]
+    #[error(transparent)]
+    Cache(#[from] cache::Error),
+    #[error("user {0} not found")]
+    NotFound(String),
+}
+
+#[macros::export]
+pub async fn should_nyaify(reader_user_id: &str) -> Result<bool, Error> {
+    let cached_value = cache::get_one::<bool>(cache::Category::CatLang, reader_user_id).await?;
+    if let Some(value) = cached_value {
+        return Ok(value);
+    }
+
+    let fetched_value = user::Entity::find_by_id(reader_user_id)
+        .select_only()
+        .select_column(user::Column::ReadCatLanguage)
+        .into_tuple::<bool>()
+        .one(db_conn().await?)
+        .await?
+        .ok_or_else(|| Error::NotFound(reader_user_id.to_owned()))?;
+
+    cache::set_one(
+        cache::Category::CatLang,
+        reader_user_id,
+        &fetched_value,
+        10 * 60,
+    )
+    .await?;
+
+    Ok(fetched_value)
+}
diff --git a/packages/backend-rs/src/model/entity/user.rs b/packages/backend-rs/src/model/entity/user.rs
index f11e1e43d9..f13dc4c9af 100644
--- a/packages/backend-rs/src/model/entity/user.rs
+++ b/packages/backend-rs/src/model/entity/user.rs
@@ -77,6 +77,8 @@ pub struct Model {
     pub is_indexable: bool,
     #[sea_orm(column_name = "alsoKnownAs")]
     pub also_known_as: Option<Vec<String>>,
+    #[sea_orm(column_name = "readCatLanguage")]
+    pub read_cat_language: bool,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/packages/backend-rs/src/model/entity/user_profile.rs b/packages/backend-rs/src/model/entity/user_profile.rs
index 818feca214..afebc6f0d7 100644
--- a/packages/backend-rs/src/model/entity/user_profile.rs
+++ b/packages/backend-rs/src/model/entity/user_profile.rs
@@ -68,13 +68,13 @@ pub struct Model {
     pub is_indexable: bool,
     #[sea_orm(column_name = "mutedPatterns")]
     pub muted_patterns: Vec<String>,
+    #[sea_orm(column_type = "JsonBinary")]
+    pub mentions: Json,
     #[sea_orm(column_name = "mutedInstances")]
     pub muted_instances: Vec<String>,
     #[sea_orm(column_name = "mutedWords")]
     pub muted_words: Vec<String>,
     pub lang: Option<String>,
-    #[sea_orm(column_type = "JsonBinary")]
-    pub mentions: Json,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 646d3bae3d..8cda70743b 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -28,18 +28,18 @@
 		"@koa/router": "12.0.1",
 		"@ladjs/koa-views": "9.0.0",
 		"@peertube/http-signature": "1.7.0",
-		"@redocly/openapi-core": "1.17.0",
+		"@redocly/openapi-core": "1.17.1",
 		"@sinonjs/fake-timers": "11.2.2",
 		"adm-zip": "0.5.14",
 		"ajv": "8.16.0",
 		"archiver": "7.0.1",
 		"async-lock": "1.4.0",
 		"async-mutex": "0.5.0",
-		"aws-sdk": "2.1653.0",
+		"aws-sdk": "2.1654.0",
 		"axios": "1.7.2",
 		"backend-rs": "workspace:*",
 		"blurhash": "2.0.5",
-		"bull": "4.15.0",
+		"bull": "4.15.1",
 		"cacheable-lookup": "git+https://github.com/TheEssem/cacheable-lookup.git#dd2fb616366a3c68dcf321a57a67295967b204bf",
 		"cbor-x": "1.5.9",
 		"chalk": "5.3.0",
@@ -174,6 +174,6 @@
 		"type-fest": "4.21.0",
 		"typescript": "5.5.3",
 		"webpack": "5.92.1",
-		"ws": "8.17.1"
+		"ws": "8.18.0"
 	}
 }
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index dbf51c08c5..801d2cf5fa 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -25,10 +25,11 @@ export async function masterMain() {
 	// initialize app
 	try {
 		greet();
-		showEnvironment();
 		showServerInfo();
+		showEnvironment();
 		showNodejsVersion();
 		await connectDb();
+		await updateMetaCache();
 	} catch (e) {
 		bootLogger.error(
 			`Fatal error occurred during initialization:\n${inspect(e)}`,
diff --git a/packages/backend/src/migration/1720107645050-turn-off-cat-language.ts b/packages/backend/src/migration/1720107645050-turn-off-cat-language.ts
new file mode 100644
index 0000000000..8503ce96e9
--- /dev/null
+++ b/packages/backend/src/migration/1720107645050-turn-off-cat-language.ts
@@ -0,0 +1,16 @@
+import type { MigrationInterface, QueryRunner } from "typeorm";
+
+export class TurnOffCatLanguage1720107645050 implements MigrationInterface {
+	public async up(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(
+			`ALTER TABLE "user" ADD COLUMN "readCatLanguage" boolean NOT NULL DEFAULT true`,
+		);
+		await queryRunner.query(
+			`COMMENT ON COLUMN "user"."readCatLanguage" IS 'Whether to enable the cat language conversion.'`,
+		);
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "readCatLanguage"`);
+	}
+}
diff --git a/packages/backend/src/misc/post.ts b/packages/backend/src/misc/post.ts
index 0b107ed009..fd89a4d7e3 100644
--- a/packages/backend/src/misc/post.ts
+++ b/packages/backend/src/misc/post.ts
@@ -1,3 +1,5 @@
+import { noteVisibilities } from "@/types.js";
+
 export type Post = {
 	text: string | undefined;
 	cw: string | null;
@@ -12,7 +14,9 @@ export function parse(acct: any): Post {
 		cw: acct.cw,
 		localOnly: acct.localOnly,
 		createdAt: new Date(acct.createdAt),
-		visibility: `hidden${acct.visibility || ""}`,
+		visibility: noteVisibilities.includes(acct.visibility)
+			? acct.visibility
+			: "specified",
 	};
 }
 
diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts
index 65780eb44d..e71e04ef8d 100644
--- a/packages/backend/src/models/entities/user.ts
+++ b/packages/backend/src/models/entities/user.ts
@@ -159,6 +159,12 @@ export class User {
 	})
 	public speakAsCat: boolean;
 
+	@Column("boolean", {
+		default: true,
+		comment: "Whether to enable the cat language conversion.",
+	})
+	public readCatLanguage: boolean;
+
 	@Column("boolean", {
 		default: false,
 		comment: "Whether the User is the admin.",
diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index b825df0049..aa4fa09340 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -14,7 +14,12 @@ import {
 	Notes,
 } from "../index.js";
 import type { Packed } from "@/misc/schema.js";
-import { countReactions, decodeReaction, nyaify } from "backend-rs";
+import {
+	countReactions,
+	decodeReaction,
+	nyaify,
+	shouldNyaify,
+} from "backend-rs";
 import { awaitAll } from "@/prelude/await-all.js";
 import type { NoteReaction } from "@/models/entities/note-reaction.js";
 import {
@@ -311,7 +316,13 @@ export const NoteRepository = db.getRepository(Note).extend({
 			mentionedRemoteUsers: this.mentionedRemoteUsers(note),
 		});
 
-		if (packed.user.isCat && packed.user.speakAsCat && packed.text) {
+		if (
+			packed.user.isCat &&
+			packed.user.speakAsCat &&
+			packed.text != null &&
+			meId != null &&
+			(await shouldNyaify(meId))
+		) {
 			const tokens = packed.text ? mfm.parse(packed.text) : [];
 			function nyaifyNode(node: mfm.MfmNode) {
 				if (node.type === "quote") return;
diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts
index c07473d0ea..e6c8f2517c 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -464,7 +464,8 @@ export const UserRepository = db.getRepository(User).extend({
 			isLocked: user.isLocked,
 			isIndexable: user.isIndexable,
 			isCat: user.isCat || falsy,
-			speakAsCat: user.speakAsCat || falsy,
+			speakAsCat: user.speakAsCat,
+			readCatLanguage: user.readCatLanguage,
 			instance: user.host
 				? userInstanceCache
 						.fetch(
diff --git a/packages/backend/src/queue/processors/db/import-firefish-post.ts b/packages/backend/src/queue/processors/db/import-firefish-post.ts
index 34a27c4c7a..8d318a0d2a 100644
--- a/packages/backend/src/queue/processors/db/import-firefish-post.ts
+++ b/packages/backend/src/queue/processors/db/import-firefish-post.ts
@@ -92,22 +92,29 @@ export async function importCkPost(
 		logger.info("Post updated");
 	}
 	if (note == null) {
-		note = await create(user, {
-			createdAt: createdAt,
-			files: files.length === 0 ? undefined : files,
-			poll: undefined,
-			text: text || undefined,
-			reply: post.replyId ? job.data.parent : null,
-			renote: post.renoteId ? job.data.parent : null,
-			cw: cw,
-			localOnly,
-			visibility: visibility,
-			visibleUsers: [],
-			channel: null,
-			apMentions: new Array(0),
-			apHashtags: undefined,
-			apEmojis: undefined,
-		});
+		note = await create(
+			user,
+			{
+				createdAt: createdAt,
+				scheduledAt: undefined,
+				files: files.length === 0 ? undefined : files,
+				poll: undefined,
+				text: text || undefined,
+				reply: post.replyId ? job.data.parent : null,
+				renote: post.renoteId ? job.data.parent : null,
+				cw: cw,
+				localOnly,
+				visibility: visibility,
+				visibleUsers: [],
+				channel: null,
+				apMentions: new Array(0),
+				apHashtags: undefined,
+				apEmojis: undefined,
+			},
+			false,
+			undefined,
+			true,
+		);
 		logger.debug("New post has been created");
 	} else {
 		logger.info("This post already exists");
diff --git a/packages/backend/src/queue/processors/db/import-masto-post.ts b/packages/backend/src/queue/processors/db/import-masto-post.ts
index 532c288dba..4bf351e8ec 100644
--- a/packages/backend/src/queue/processors/db/import-masto-post.ts
+++ b/packages/backend/src/queue/processors/db/import-masto-post.ts
@@ -10,6 +10,11 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
 import { Notes, NoteEdits } from "@/models/index.js";
 import type { Note } from "@/models/entities/note.js";
 import { genId } from "backend-rs";
+import promiseLimit from "promise-limit";
+import { unique, concat } from "@/prelude/array.js";
+import type { CacheableUser } from "@/models/entities/user.js";
+import { resolvePerson } from "@/remote/activitypub/models/person.js";
+import { isPublic } from "@/remote/activitypub/audience.js";
 
 const logger = queueLogger.createSubLogger("import-masto-post");
 
@@ -118,24 +123,57 @@ export async function importMastoPost(
 		logger.info("Post updated");
 	}
 	if (note == null) {
-		note = await create(user, {
-			createdAt: isRenote
-				? new Date(post.published)
-				: new Date(post.object.published),
-			files: files.length === 0 ? undefined : files,
-			poll: undefined,
-			text: text || undefined,
-			reply,
-			renote,
-			cw: !isRenote && post.object.sensitive ? post.object.summary : undefined,
-			localOnly: false,
-			visibility: "hiddenpublic",
-			visibleUsers: [],
-			channel: null,
-			apMentions: new Array(0),
-			apHashtags: undefined,
-			apEmojis: undefined,
-		});
+		let visibility = "specified";
+		let visibleUsers: CacheableUser[] = [];
+		if ((post.to as string[]).some(isPublic)) {
+			visibility = "public";
+		} else if ((post.cc as string[]).some(isPublic)) {
+			visibility = "home";
+		} else if ((post.cc as string[]).some((cc) => cc.endsWith("/followers"))) {
+			visibility = "followers";
+		} else {
+			try {
+				const visibleUsersList = unique(concat([post.to, post.cc]));
+
+				const limit = promiseLimit<CacheableUser | null>(2);
+				visibleUsers = (
+					await Promise.all(
+						visibleUsersList.map((id) =>
+							limit(() => resolvePerson(id).catch(() => null)),
+						),
+					)
+				).filter((x): x is CacheableUser => x != null);
+			} catch {
+				// nothing need to do.
+			}
+		}
+
+		note = await create(
+			user,
+			{
+				createdAt: isRenote
+					? new Date(post.published)
+					: new Date(post.object.published),
+				scheduledAt: undefined,
+				files: files.length === 0 ? undefined : files,
+				poll: undefined,
+				text: text || undefined,
+				reply,
+				renote,
+				cw:
+					!isRenote && post.object.sensitive ? post.object.summary : undefined,
+				localOnly: false,
+				visibility,
+				visibleUsers,
+				channel: null,
+				apMentions: new Array(0),
+				apHashtags: undefined,
+				apEmojis: undefined,
+			},
+			false,
+			undefined,
+			true,
+		);
 		logger.debug("New post has been created");
 	} else {
 		logger.info("This post already exists");
diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts
index 9d840bc574..ec4ac6d317 100644
--- a/packages/backend/src/remote/activitypub/audience.ts
+++ b/packages/backend/src/remote/activitypub/audience.ts
@@ -90,7 +90,7 @@ function groupingAudience(ids: string[], actor: CacheableRemoteUser) {
 	return groups;
 }
 
-function isPublic(id: string) {
+export function isPublic(id: string) {
 	return [
 		"https://www.w3.org/ns/activitystreams#Public",
 		"as:Public",
@@ -98,6 +98,6 @@ function isPublic(id: string) {
 	].includes(id);
 }
 
-function isFollowers(id: string, actor: CacheableRemoteUser) {
+export function isFollowers(id: string, actor: CacheableRemoteUser) {
 	return id === (actor.followersUri || `${actor.uri}/followers`);
 }
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 98d760ddb5..dd0511cfed 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -114,6 +114,7 @@ export const paramDef = {
 		isBot: { type: "boolean" },
 		isCat: { type: "boolean" },
 		speakAsCat: { type: "boolean", nullable: true },
+		readCatLanguage: { type: "boolean", nullable: true },
 		isIndexable: { type: "boolean" },
 		injectFeaturedNote: { type: "boolean" },
 		receiveAnnouncementEmail: { type: "boolean" },
@@ -220,6 +221,8 @@ export default define(meta, paramDef, async (ps, _user, token) => {
 		profileUpdates.isIndexable = ps.isIndexable;
 	}
 	if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
+	if (typeof ps.readCatLanguage === "boolean")
+		updates.readCatLanguage = ps.readCatLanguage;
 	if (typeof ps.injectFeaturedNote === "boolean")
 		profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
 	if (typeof ps.receiveAnnouncementEmail === "boolean")
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 03ca4f7578..c5b71925a0 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -162,11 +162,12 @@ export default async (
 	data: NoteLike,
 	silent = false,
 	waitToPublish?: (note: Note) => Promise<void>,
+	dontFederateInitially = false,
 ) =>
 	// biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
 	new Promise<Note>(async (res, rej) => {
-		const dontFederateInitially =
-			data.visibility?.startsWith("hidden") === true;
+		dontFederateInitially =
+			dontFederateInitially || data.visibility?.startsWith("hidden");
 
 		// Whether this is a scheduled "draft" post (yet to be published)
 		const isDraft = data.scheduledAt != null;
@@ -204,8 +205,6 @@ export default async (
 		if (data.channel != null) data.visibility = "public";
 		if (data.channel != null) data.visibleUsers = [];
 		if (data.channel != null) data.localOnly = true;
-		if (data.visibility.startsWith("hidden") && data.visibility !== "hidden")
-			data.visibility = data.visibility.slice(6);
 
 		// enforce silent clients on server
 		if (
diff --git a/packages/client/src/components/MkPostSearch.vue b/packages/client/src/components/MkPostSearch.vue
index 7c732ca2c6..0c56a235ba 100644
--- a/packages/client/src/components/MkPostSearch.vue
+++ b/packages/client/src/components/MkPostSearch.vue
@@ -78,7 +78,7 @@
 				<MkButton inline primary @click="search"
 					>{{ i18n.ts.search }}
 				</MkButton>
-				<MkButton inline @click="lookup">{{ i18n.ts.lookup }}</MkButton>
+				<MkButton inline primary @click="lookup">{{ i18n.ts.lookup }}</MkButton>
 				<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
 			</div>
 		</div>
diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts
index 16750c26e0..51d837c959 100644
--- a/packages/client/src/init.ts
+++ b/packages/client/src/init.ts
@@ -61,9 +61,11 @@ function checkForSplash() {
 	if (splash) {
 		splash.style.opacity = "0";
 		splash.style.pointerEvents = "none";
-		splash.addEventListener("transitionend", () => {
+
+		// remove splash screen
+		window.setTimeout(() => {
 			splash.remove();
-		});
+		}, 1000);
 	}
 }
 
diff --git a/packages/client/src/instance.ts b/packages/client/src/instance.ts
index f4ea4d431e..10249d909f 100644
--- a/packages/client/src/instance.ts
+++ b/packages/client/src/instance.ts
@@ -5,18 +5,76 @@ import { set, get } from "idb-keyval";
 
 // TODO: 他のタブと永続化されたstateを同期
 
-// TODO: get("instance") requires top-level await
-let instance: entities.DetailedInstanceMetadata;
+// TODO: fallback to defaults more nicely (with #10947)
+// default values
+let instanceMeta: entities.DetailedInstanceMetadata = {
+	maintainerName: "",
+	maintainerEmail: "",
+	version: "",
+	name: null,
+	uri: "",
+	tosUrl: null,
+	description: null,
+	disableRegistration: true,
+	disableLocalTimeline: false,
+	disableGlobalTimeline: false,
+	disableRecommendedTimeline: true,
+	enableGuestTimeline: false,
+	driveCapacityPerLocalUserMb: 1000,
+	driveCapacityPerRemoteUserMb: 0,
+	antennaLimit: 5,
+	enableHcaptcha: false,
+	hcaptchaSiteKey: null,
+	enableRecaptcha: false,
+	recaptchaSiteKey: null,
+	swPublickey: null,
+	maxNoteTextLength: 3000,
+	maxCaptionTextLength: 1500,
+	enableEmail: false,
+	enableServiceWorker: false,
+	markLocalFilesNsfwByDefault: false,
+	emojis: [],
+	ads: [],
+	langs: [],
+	moreUrls: [],
+	repositoryUrl: "https://firefish.dev/firefish/firefish",
+	feedbackUrl: "https://firefish.dev/firefish/firefish/-/issues",
+	defaultDarkTheme: null,
+	defaultLightTheme: null,
+	defaultReaction: "⭐",
+	cacheRemoteFiles: false,
+	proxyAccountName: null,
+	emailRequiredForSignup: false,
+	mascotImageUrl: "",
+	bannerUrl: "",
+	backgroundImageUrl: "",
+	errorImageUrl: "",
+	iconUrl: null,
+	requireSetup: false,
+	translatorAvailable: false,
+	features: {
+		registration: false,
+		localTimeLine: true,
+		recommendedTimeLine: false,
+		globalTimeLine: true,
+		searchFilters: true,
+		hcaptcha: false,
+		recaptcha: false,
+		objectStorage: false,
+		serviceWorker: false,
+	},
+};
 
+// get("instanceMeta") requires top-level await
 export function getInstanceInfo(): entities.DetailedInstanceMetadata {
-	return instance;
+	return instanceMeta;
 }
 
 export async function initializeInstanceCache(): Promise<void> {
 	// Is the data stored in IndexDB?
-	const fromIdb = await get<string>("instance");
+	const fromIdb = await get<string>("instanceMeta");
 	if (fromIdb != null) {
-		instance = JSON.parse(fromIdb);
+		instanceMeta = JSON.parse(fromIdb);
 	}
 	// Call API
 	updateInstanceCache();
@@ -27,29 +85,25 @@ export async function updateInstanceCache(): Promise<void> {
 		detail: true,
 	});
 
-	// TODO: set default values
-	instance = {} as entities.DetailedInstanceMetadata;
-
 	for (const [k, v] of Object.entries(meta)) {
-		instance[k] = v;
+		instanceMeta[k] = v;
 	}
-
-	set("instance", JSON.stringify(instance));
+	set("instanceMeta", JSON.stringify(instanceMeta));
 }
 
 export const emojiCategories = computed(() => {
-	if (instance.emojis == null) return [];
+	if (instanceMeta.emojis == null) return [];
 	const categories = new Set();
-	for (const emoji of instance.emojis) {
+	for (const emoji of instanceMeta.emojis) {
 		categories.add(emoji.category);
 	}
 	return Array.from(categories);
 });
 
 export const emojiTags = computed(() => {
-	if (instance.emojis == null) return [];
+	if (instanceMeta.emojis == null) return [];
 	const tags = new Set();
-	for (const emoji of instance.emojis) {
+	for (const emoji of instanceMeta.emojis) {
 		for (const tag of emoji.aliases) {
 			tags.add(tag);
 		}
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index 54b9f4aa62..aa6d2a9ad5 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -200,6 +200,11 @@
 					i18n.ts.expandOnNoteClickDesc
 				}}</template>
 			</FormSwitch>
+			<FormSwitch v-model="turnOffCatLanguage" @update:modelValue="save()" class="_formBlock"
+				>{{ i18n.ts.turnOffCatLanguage }}<template #caption>{{
+					i18n.ts.reflectMayTakeTime
+				}}</template>
+			</FormSwitch>
 			<FormSwitch v-model="advancedMfm" class="_formBlock">
 				{{ i18n.ts._mfm.advanced
 				}}<template #caption>{{
@@ -423,6 +428,13 @@ const serverLang = ref(me?.lang);
 const translateLang = ref(localStorage.getItem("translateLang"));
 const fontSize = ref(localStorage.getItem("fontSize"));
 const useSystemFont = ref(localStorage.getItem("useSystemFont") !== "f");
+const turnOffCatLanguage = ref(!me?.readCatLanguage);
+
+function save() {
+	os.api("i/update", {
+		readCatLanguage: !turnOffCatLanguage.value,
+	});
+}
 
 async function reloadAsk() {
 	const { canceled } = await os.confirm({
diff --git a/packages/client/src/ui/visitor/b.vue b/packages/client/src/ui/visitor/b.vue
index fb8bfe5aaf..0d1f6a77d5 100644
--- a/packages/client/src/ui/visitor/b.vue
+++ b/packages/client/src/ui/visitor/b.vue
@@ -110,6 +110,10 @@ provideMetadataReceiver((info) => {
 });
 
 const root = computed(() => mainRouter.currentRoute.value?.name === "index");
+const showMenu = ref(false);
+const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
+const narrow = ref(window.innerWidth < 1280);
+const meta = ref();
 
 os.api("meta", { detail: true }).then((res) => {
 	meta.value = res;
diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts
index 7dc35122bd..eccab27082 100644
--- a/packages/firefish-js/src/api.types.ts
+++ b/packages/firefish-js/src/api.types.ts
@@ -598,6 +598,8 @@ export type Endpoints = {
 			preventAiLearning?: boolean;
 			isBot?: boolean;
 			isCat?: boolean;
+			speakAsCat?: boolean;
+			readCatLanguage?: boolean;
 			injectFeaturedNote?: boolean;
 			receiveAnnouncementEmail?: boolean;
 			alwaysMarkNsfw?: boolean;
diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts
index 7da946ba95..83ebc2514c 100644
--- a/packages/firefish-js/src/entities.ts
+++ b/packages/firefish-js/src/entities.ts
@@ -30,6 +30,7 @@ export type UserLite = {
 	isIndexable: boolean;
 	isCat?: boolean;
 	speakAsCat?: boolean;
+	readCatLanguage?: boolean;
 	driveCapacityOverrideMb: number | null;
 };
 
@@ -395,7 +396,7 @@ export type DetailedInstanceMetadata = LiteInstanceMetadata & {
 		miauth?: boolean;
 	};
 	langs: string[];
-	moreUrls: object;
+	moreUrls: { name: string; url: string }[];
 	repositoryUrl: string;
 	feedbackUrl: string;
 	defaultDarkTheme: string | null;
diff --git a/packages/firefish-js/src/schema/user.ts b/packages/firefish-js/src/schema/user.ts
index c7417ce0e4..702a8b380a 100644
--- a/packages/firefish-js/src/schema/user.ts
+++ b/packages/firefish-js/src/schema/user.ts
@@ -76,6 +76,11 @@ export const packedUserLiteSchema = {
 			nullable: false,
 			optional: true,
 		},
+		readCatLanguage: {
+			type: "boolean",
+			nullable: false,
+			optional: true,
+		},
 		emojis: {
 			type: "array",
 			nullable: false,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5fe583c4ca..43500ac9e6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -67,8 +67,8 @@ importers:
         specifier: 1.7.0
         version: 1.7.0
       '@redocly/openapi-core':
-        specifier: 1.17.0
-        version: 1.17.0
+        specifier: 1.17.1
+        version: 1.17.1
       '@sinonjs/fake-timers':
         specifier: 11.2.2
         version: 11.2.2
@@ -88,8 +88,8 @@ importers:
         specifier: 0.5.0
         version: 0.5.0
       aws-sdk:
-        specifier: 2.1653.0
-        version: 2.1653.0
+        specifier: 2.1654.0
+        version: 2.1654.0
       axios:
         specifier: 1.7.2
         version: 1.7.2
@@ -100,8 +100,8 @@ importers:
         specifier: 2.0.5
         version: 2.0.5
       bull:
-        specifier: 4.15.0
-        version: 4.15.0
+        specifier: 4.15.1
+        version: 4.15.1
       cacheable-lookup:
         specifier: git+https://github.com/TheEssem/cacheable-lookup.git#dd2fb616366a3c68dcf321a57a67295967b204bf
         version: https://codeload.github.com/TheEssem/cacheable-lookup/tar.gz/dd2fb616366a3c68dcf321a57a67295967b204bf
@@ -500,8 +500,8 @@ importers:
         specifier: 5.92.1
         version: 5.92.1
       ws:
-        specifier: 8.17.1
-        version: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+        specifier: 8.18.0
+        version: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
 
   packages/backend-rs:
     devDependencies:
@@ -2099,8 +2099,8 @@ packages:
   '@redocly/config@0.6.2':
     resolution: {integrity: sha512-c3K5u64eMnr2ootPcpEI0ioIRLE8QP8ptvLxG9MwAmb2sU8HMRfVwXDU3AZiMVY2w4Ts0mDc+Xv4HTIk8DRqFw==}
 
-  '@redocly/openapi-core@1.17.0':
-    resolution: {integrity: sha512-XoNIuksnOGAzAcfpyJkHrMxwurXaQfglnovNE7/pTx4OEjik3OT91+tKAyRCkklVCdMtAA3YokGMZzdhjViUWA==}
+  '@redocly/openapi-core@1.17.1':
+    resolution: {integrity: sha512-PQxDLLNk5cBatJBBxvfk49HFw/nVozw1XZ6Dw/GX0Tviq+WxeEjEuLAKfnLVvb5L0wgs4TNmVG4Y+JyofSPu1A==}
     engines: {node: '>=14.19.0', npm: '>=7.0.0'}
 
   '@rollup/plugin-alias@5.1.0':
@@ -2839,8 +2839,8 @@ packages:
     resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
     engines: {node: '>= 0.4'}
 
-  aws-sdk@2.1653.0:
-    resolution: {integrity: sha512-9f42kuLpMcL1EPZOsLM8u6wlnOMtFwED1b24SN0fBbi/N7N1xTLZ7vbEMt/haz06Lc3Vr3VMDyv0atfMmkboBw==}
+  aws-sdk@2.1654.0:
+    resolution: {integrity: sha512-b5ryvXipBJod9Uh1GUfQNgi5tIIiluxJbyqr/hZ/mr5U8WxrrfjVq3nGnx5JjevFKYRqXIywhumsVyanfACzFA==}
     engines: {node: '>= 10.0.0'}
 
   axios@0.24.0:
@@ -2981,8 +2981,8 @@ packages:
     resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
     engines: {node: '>=6.14.2'}
 
-  bull@4.15.0:
-    resolution: {integrity: sha512-nOEAfUXwUXtFbRPQP3bWCwpQ/NAerAu2Nym/ucv5C1E+Qh2x6RGdKKsYIfZam4mYncayTynTUN/HLhRgGi2N8w==}
+  bull@4.15.1:
+    resolution: {integrity: sha512-knVKiZdrXbRkB+fWqNryDz85b3JfsT3dBrZexkztwvTH/AFmpHvsC933VB3JX18aJCz47E+xdO57xbDvxljoAg==}
     engines: {node: '>=12'}
 
   busboy@1.6.0:
@@ -6856,8 +6856,8 @@ packages:
     resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
 
-  ws@8.17.1:
-    resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+  ws@8.18.0:
+    resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
     engines: {node: '>=10.0.0'}
     peerDependencies:
       bufferutil: ^4.0.1
@@ -8229,7 +8229,7 @@ snapshots:
 
   '@redocly/config@0.6.2': {}
 
-  '@redocly/openapi-core@1.17.0':
+  '@redocly/openapi-core@1.17.1':
     dependencies:
       '@redocly/ajv': 8.11.0
       '@redocly/config': 0.6.2
@@ -9028,7 +9028,7 @@ snapshots:
     dependencies:
       possible-typed-array-names: 1.0.0
 
-  aws-sdk@2.1653.0:
+  aws-sdk@2.1654.0:
     dependencies:
       buffer: 4.9.2
       events: 1.1.1
@@ -9223,7 +9223,7 @@ snapshots:
     dependencies:
       node-gyp-build: 4.8.1
 
-  bull@4.15.0:
+  bull@4.15.1:
     dependencies:
       cron-parser: 4.9.0
       get-port: 5.1.1
@@ -11130,7 +11130,7 @@ snapshots:
       whatwg-encoding: 3.1.1
       whatwg-mimetype: 4.0.0
       whatwg-url: 14.0.0
-      ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       xml-name-validator: 5.0.0
     transitivePeerDependencies:
       - bufferutil
@@ -13323,7 +13323,7 @@ snapshots:
       imurmurhash: 0.1.4
       signal-exit: 3.0.7
 
-  ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+  ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
     optionalDependencies:
       bufferutil: 4.0.8
       utf-8-validate: 5.0.10