このコミットが含まれているのは:
2026-10-28 20:57:22 +09:00
コミット 614a12e7aa
18個のファイルの変更、175行の追加、107行の削除
+60 -26
ファイルの表示
@@ -148,10 +148,8 @@ const nested: Partial<Record<CountColumn, CountField[]>> = {
{ key: 'id', label: 'counts:columns.resourceId' },
{ key: 'versionNo', label: 'history:version' },
{ key: 'kind', label: 'counts:columns.kind' },
{ key: 'name', label: 'history:name' },
{ key: 'category', label: 'common:categoryLabel' },
{ key: 'sections', label: 'counts:columns.sections' },
{ key: 'names', label: 'counts:columns.names', children: nameFields }],
{ key: 'sections', label: 'counts:columns.sections' }],
names: nameFields,
aliases: [{ key: 'languageCode', label: 'counts:columns.language' }, ...aliasFields],
parents: [{ key: 'id', label: 'counts:columns.resourceId' }] }
@@ -167,7 +165,8 @@ export const columnsForCounts = (
? ['title', 'url', 'thumbnail_base', 'tags', 'parents', 'video_ms',
'original_created_from', 'original_created_before']
: (resource === 'tags'
? ['name', 'names', 'category', 'aliases', 'parents', 'deprecated']
? [...(definition?.nameColumns?.map (field => field.key) ?? []),
'category', 'parents', 'deprecated']
: (definition?.fields.filter (field => ![
'resource_id', 'version_no', 'event_type', 'user', 'created_at']
.includes (field.key)).map (field => field.key) ?? []))),
@@ -182,10 +181,8 @@ export const fieldsForCounts = (
key,
label: (key === 'parents' && resource === 'tags') ? 'tags:parents'
: (countColumns[key] ?? countsFieldLabel (key)),
children: key === 'parents' && resource === 'tags'
? [...(nested.parents ?? []), { key: 'name', label: 'counts:columns.name' },
{ key: 'names', label: 'counts:columns.names', children: nameFields }]
: nested[key] }))
children: (key === 'tags' || (key === 'parents' && resource === 'tags'))
? [...(nested[key] ?? []), ...(definition?.nameColumns ?? [])] : nested[key] }))
export const orderedCountFields = (
@@ -235,7 +232,9 @@ export const selectedCountColumns = (
const common = ['resource_id', 'version_no', 'event_type', 'user', 'created_at']
const defaults: Record<CountsResource, string[]> = {
posts: ['title', 'url', 'tags', 'parents', 'video_ms'],
tags: ['name', 'category', 'aliases', 'parents'],
tags: [...(definition?.nameColumns ?? []).filter (field =>
field.nameLocale?.code === currentViewingLocale ()).map (field => field.key),
'category', 'parents'],
nico_tags: ['name', 'linked_tags'],
tag_descriptions: ['language_code', 'body', 'source_language_code'],
tag_groups: ['name', 'description', 'members'],
@@ -247,16 +246,39 @@ export const selectedCountColumns = (
}
export const countChildPaths = (fields: CountField[], prefix = ''): string[] =>
export const countChildPaths = (
fields: CountField[], prefix = '', leavesOnly = false,
): string[] =>
fields.flatMap (field => {
const path = prefix ? `${prefix}.${field.key}` : field.key
return [path, ...countChildPaths (field.children ?? [], path)]
return [...((leavesOnly && field.children != null) ? [] : [path]),
...countChildPaths (field.children ?? [], path, leavesOnly)]
})
export const selectedCountChildren = (params: URLSearchParams): Set<string> => {
export const selectedCountChildren = (
params: URLSearchParams, fields?: CountField[],
): Set<string> => {
const selected = new Set (params.has ('children') ? params.get ('children')!.split (' ')
: ['tags.name', 'parents.id', 'aliases.name', 'members.tagId'])
if (fields != null)
{
const valid = new Set (countChildPaths (fields).filter (path => path.includes ('.')))
for (const path of selected) if (!valid.has (path)) selected.delete (path)
const visit = (fields: CountField[], prefix = '') => {
for (const field of fields)
{
const path = prefix ? `${prefix}.${field.key}` : field.key
if (field.children == null) continue
visit (field.children, path)
if (prefix === '') continue
if (field.children.some (child => selected.has (`${path}.${child.key}`))) selected.add (path)
else selected.delete (path)
}
}
visit (fields)
return selected
}
const clean = (fields: CountField[], prefix: string) => {
for (const field of fields)
{
@@ -344,6 +366,15 @@ export const normaliseCountsParams = (
field.multipleValues && field.nameLocale?.code === currentViewingLocale ())?.key
const rootKey = (key: string) => (key === 'name' && current != null) ? current
: ((key === 'aliases' && aliases != null) ? aliases : key)
const childKeys = (key: string): string[] => {
const [parent, field, ...rest] = key.split ('.')
if (parent !== 'tags' && (parent !== 'parents' || resource !== 'tags')) return [key]
if (field === 'name' && current != null) return [`${parent}.${current}`]
if (field === 'names')
return names.map (name => `${parent}.${name}`)
if (field === 'aliases' && aliases != null) return [`${parent}.${aliases}`]
return rest.length > 0 ? [] : [key]
}
if (resource === 'tags' && params.has ('columns'))
params.set ('columns', params.get ('columns')!.split (' ').flatMap (key =>
key === 'names' ? names : [rootKey (key)])
@@ -351,18 +382,12 @@ export const normaliseCountsParams = (
if (params.has ('children'))
{
const selected = params.get ('children')!.split (' ')
params.set ('children', selected.flatMap (key => {
if (['tags.names', 'parents.names'].includes (key))
return names.map (name => `${key.slice (0, -6)}.${name}`)
if (['tags.aliases', 'parents.aliases'].includes (key) && aliases != null)
return [`${key.slice (0, -8)}.${aliases}`]
if (key.startsWith ('tags.aliases.') || key.startsWith ('parents.aliases.')) return []
return key.startsWith ('tags.names.') || key.startsWith ('parents.names.') ? [] : [key]
}).join (' '))
params.set ('children', [...new Set (selected.flatMap (childKeys))].join (' '))
}
if (resource === 'tags' && params.has ('order'))
if (params.has ('order'))
params.set ('order', params.get ('order')!.split (' ').flatMap (key =>
key === 'names' ? names : [rootKey (key)])
key.includes ('.') ? childKeys (key)
: ((resource === 'tags') ? (key === 'names' ? names : [rootKey (key)]) : [key]))
.join (' '))
}
for (const [key, value] of params)
@@ -419,16 +444,25 @@ export const normaliseCountsParams = (
}
params.set (key, value)
}
const children = selectedCountChildren (params)
if (params.has ('children')) params.set ('children', [...children].filter (Boolean).join (' '))
if (params.has ('columns'))
const fields = fieldsForCounts (resource, definition)
const children = selectedCountChildren (params, definition == null ? undefined : fields)
if (params.has ('columns') && definition != null)
{
const fields = fieldsForCounts (resource, definition)
params.set ('columns', params.get ('columns')!.split (' ').filter (key => {
const field = fields.find (field => field.key === key)
return field != null && (field.children == null
|| field.children.some (child => children.has (`${key}.${child.key}`)))
}).join (' '))
const roots = new Set (params.get ('columns')!.split (' '))
for (const path of children)
if (!roots.has (path.split ('.')[0])) children.delete (path)
}
if (params.has ('children')) params.set ('children', [...children].filter (Boolean).join (' '))
if (params.has ('order') && definition != null)
{
const valid = new Set (countChildPaths (fields))
params.set ('order', [...new Set (params.get ('order')!.split (' '))]
.filter (key => valid.has (key)).join (' '))
}
return params
}
+2 -4
ファイルの表示
@@ -1,6 +1,6 @@
import { createContext, useContext } from 'react'
import { i18n, translateUIMessage } from '@/lib/i18n'
import { languageUIMessage, translateUIMessage } from '@/lib/i18n'
import { apiGet } from '@/lib/api'
import type { LocaleCode } from '@/lib/locales'
@@ -52,9 +52,7 @@ const localiseCountsDefinition = (
): CountsDefinition => {
const heading = (name: CountField['nameLocale'], aliasesFlg: boolean) => {
if (name == null) return null
const key = `languageNames.${name.languageCode}`
const language = i18n.exists (key, { ns: 'counts', lng: locale })
? translateUIMessage (key, 'counts', locale).text : name.languageCode
const language = languageUIMessage (name.languageCode, locale).text
const scripts = new Set ((definition.nameColumns ?? [])
.filter (field => field.nameLocale?.languageCode === name.languageCode)
.map (field => field.nameLocale!.scriptCode.toLowerCase ()))
+10 -9
ファイルの表示
@@ -1,7 +1,7 @@
import { fieldsForCounts, orderedCountFields, selectedCountChildren,
sortedCountChildren } from '@/lib/counts'
import { countsEventTypes, countsFieldLabel } from '@/lib/countsDefinitions'
import { translateUIMessage } from '@/lib/i18n'
import { languageUIMessage, translateUIMessage } from '@/lib/i18n'
import { localisedDateString, msToTime } from '@/lib/utils'
import type { CountColumn, CountField, CountRecord, CountsResult,
@@ -133,15 +133,14 @@ export const countsLogicalName = (
const code = String (value)
if (field === 'languageCode' || field === 'scriptCode')
{
if (field === 'languageCode' && ['ja', 'ain'].includes (code))
return translateUIMessage (`history:languageNames.${code}`, 'counts', locale)
const master = row[field === 'languageCode' ? 'languageName' : 'scriptName']
if (field === 'languageCode') return languageUIMessage (code, locale)
const master = row.scriptName
if (typeof master === 'string' && master.trim () !== ''
&& master.toLowerCase () !== code.toLowerCase ())
return { text: master }
try
{
const type = field === 'languageCode' ? 'language' : 'script'
const type = 'script'
if (!displayNames.has (type))
displayNames.set (type, new Intl.DisplayNames (['ja'], { type, fallback: 'none' }))
const text = displayNames.get (type)!.of (code)
@@ -305,9 +304,9 @@ export const countsExportLabels = (
`common:categories.${key}`, 'counts', locale).text
for (const key of ['true', 'false'])
labels[`boolean:${key}`] = translateUIMessage (`boolean.${key}`, 'counts', locale).text
for (const key of ['ja', 'ain'])
labels[`language:${key}`] = translateUIMessage (
`history:languageNames.${key}`, 'counts', locale).text
for (const key of new Set (['ja', 'ain', 'en', ...(definition?.nameColumns ?? [])
.map (field => field.nameLocale?.languageCode).filter (code => code != null)]))
labels[`language:${key}`] = languageUIMessage (key, locale).text
labels.id = translateUIMessage ('columns.resourceId', 'counts', locale).text
labels.name = translateUIMessage ('history:name', 'counts', locale).text
for (const event of countsEventTypes)
@@ -403,11 +402,13 @@ export const countsSummaryValue = (
const message = (key: string, values?: Record<string, string | number>) =>
translateUIMessage (key, 'counts', locale, values).text
if (value == null) return axis.field === 'user' ? message ('bot') : ''
if (['language_code', 'source_language_code'].includes (axis.field))
return languageUIMessage (String (value), locale).text
const logical = data.axisLabels?.[`a${index}`]?.[String (value)]
if (axis.field === 'user') return message ('actorWithId', {
name: logical || message ('notRegistered'), id: value })
if (logical != null) return logical
if (['tags', 'language_code', 'source_language_code'].includes (axis.field))
if (axis.field === 'tags')
return message ('notRegistered')
if (axis.field === 'changed_field') return message (countsFieldLabel (String (value)))
if (axis.field === 'event_type') return message (`events.${value}`)
+10
ファイルの表示
@@ -111,6 +111,16 @@ export const translateUIMessage = (
}
export const languageUIMessage = (
code: string, locale: string, instance: I18n = i18n,
): { text: string; language: string } => {
const key = `languageNames.${code}`
return instance.exists (key, { ns: 'common', lng: locale })
? translateUIMessage (key, 'common', locale, { }, instance)
: { text: code, language: 'und' }
}
export const untranslatedKeys = (
locale: LocaleCode,
namespace = 'navigation',
-3
ファイルの表示
@@ -14,9 +14,6 @@ export const fetchLocales = (): Promise<{ locales: AvailableLocale[] }> =>
apiGet ('/locales')
export const LOCALE_LABELS: Record<LocaleCode, string> = {
ja: '日本語', 'ain-Kana': 'ヤウンモシルン アイヌ イタㇰ' }
export const isLocaleCode = (value: unknown): value is LocaleCode =>
SUPPORTED_LOCALES.some (locale => locale === value)