Skip to content

Commit eb7eb40

Browse files
committed
pluralize all the nouns!
1 parent 2f83116 commit eb7eb40

File tree

14 files changed

+110
-46
lines changed

14 files changed

+110
-46
lines changed

common/src/util/__tests__/string.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
11
import { describe, expect, it } from 'bun:test'
22

33
import { EXISTING_CODE_MARKER } from '../../constants'
4-
import { replaceNonStandardPlaceholderComments } from '../string'
4+
import { pluralize, replaceNonStandardPlaceholderComments } from '../string'
5+
6+
describe('pluralize', () => {
7+
it('should handle singular and plural cases correctly', () => {
8+
expect(pluralize(1, 'test')).toBe('1 test')
9+
expect(pluralize(0, 'test')).toBe('0 tests')
10+
expect(pluralize(2, 'test')).toBe('2 tests')
11+
})
12+
13+
it('should handle words ending in y', () => {
14+
expect(pluralize(1, 'city')).toBe('1 city')
15+
expect(pluralize(2, 'city')).toBe('2 cities')
16+
expect(pluralize(3, 'repository')).toBe('3 repositories')
17+
})
18+
19+
it('should handle words ending in f/fe', () => {
20+
expect(pluralize(1, 'leaf')).toBe('1 leaf')
21+
expect(pluralize(2, 'leaf')).toBe('2 leaves')
22+
expect(pluralize(1, 'knife')).toBe('1 knife')
23+
expect(pluralize(2, 'knife')).toBe('2 knives')
24+
expect(pluralize(1, 'life')).toBe('1 life')
25+
expect(pluralize(3, 'life')).toBe('3 lives')
26+
})
27+
28+
it('should handle words ending in s, sh, ch, x, z, o', () => {
29+
expect(pluralize(2, 'bus')).toBe('2 buses')
30+
expect(pluralize(2, 'box')).toBe('2 boxes')
31+
expect(pluralize(2, 'church')).toBe('2 churches')
32+
expect(pluralize(2, 'dish')).toBe('2 dishes')
33+
})
34+
35+
it('should handle regular plurals', () => {
36+
expect(pluralize(1, 'agent')).toBe('1 agent')
37+
expect(pluralize(0, 'agent')).toBe('0 agents')
38+
expect(pluralize(5, 'member')).toBe('5 members')
39+
expect(pluralize(10, 'invitation')).toBe('10 invitations')
40+
})
41+
})
542

643
describe('replaceNonStandardPlaceholderComments', () => {
744
it('should replace C-style comments', () => {

common/src/util/string.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ export const randBoolFromStr = (str: string) => {
109109
export const pluralize = (count: number, word: string) => {
110110
if (count === 1) return `${count} ${word}`
111111

112+
// Handle words ending in f/fe first (before other rules)
113+
if (word.endsWith('f')) {
114+
return `${count} ${word.slice(0, -1) + 'ves'}`
115+
}
116+
if (word.endsWith('fe')) {
117+
return `${count} ${word.slice(0, -2) + 'ves'}`
118+
}
119+
112120
// Handle words ending in 'y' (unless preceded by a vowel)
113121
if (word.endsWith('y') && !word.match(/[aeiou]y$/)) {
114122
return `${count} ${word.slice(0, -1) + 'ies'}`
@@ -119,14 +127,6 @@ export const pluralize = (count: number, word: string) => {
119127
return `${count} ${word + 'es'}`
120128
}
121129

122-
// Handle words ending in f/fe
123-
if (word.endsWith('f')) {
124-
return `${count} ${word.slice(0, -1) + 'ves'}`
125-
}
126-
if (word.endsWith('fe')) {
127-
return `${count} ${word.slice(0, -2) + 'ves'}`
128-
}
129-
130130
return `${count} ${word + 's'}`
131131
}
132132

packages/code-map/src/parse.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs'
22
import * as path from 'path'
33

4+
import { pluralize } from '@codebuff/common/util/string'
45
import { uniq } from 'lodash'
56

67
import { getLanguageConfig } from './languages'
@@ -68,7 +69,6 @@ export async function getFileTokenScores(
6869
}
6970
}
7071
}
71-
7272
// Build a map of tokens to their defining files for O(1) lookup
7373
const tokenDefinitionMap = new Map<string, string>()
7474
const highestScores = new Map<string, number>()
@@ -128,7 +128,9 @@ export async function getFileTokenScores(
128128

129129
if (DEBUG_PARSING) {
130130
const endTime = Date.now()
131-
console.log(`Parsed ${filePaths.length} files in ${endTime - startTime}ms`)
131+
console.log(
132+
`Parsed ${pluralize(filePaths.length, 'file')} in ${endTime - startTime}ms`,
133+
)
132134

133135
fs.writeFileSync(
134136
'../debug/debug-parse.json',

scripts/calculate-dau.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { db } from '@codebuff/common/db'
22
import * as schema from '@codebuff/common/db/schema'
3+
import { pluralize } from '@codebuff/common/util/string'
34
import { sql } from 'drizzle-orm'
45

56
async function calculateDAU() {
@@ -24,7 +25,7 @@ async function calculateDAU() {
2425
dailyStats.forEach((stat) => {
2526
const users = parseInt(stat.uniqueUsers)
2627
totalUsers += users
27-
console.log(`${stat.date}: ${users} users`)
28+
console.log(`${stat.date}: ${pluralize(users, 'user')}`)
2829
})
2930

3031
// Calculate and print average

scripts/export-user-emails.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { writeFileSync } from 'fs'
22

33
import db from '@codebuff/common/db'
44
import * as schema from '@codebuff/common/db/schema'
5+
import { pluralize } from '@codebuff/common/util/string'
56

67
async function exportUserEmails(): Promise<void> {
78
console.log('Exporting user emails...\n')
@@ -43,7 +44,9 @@ async function exportUserEmails(): Promise<void> {
4344
const filename = `user-emails-${timestamp}.csv`
4445
writeFileSync(filename, csvContent)
4546

46-
console.log(`\nExported ${users.length} user emails to ${filename}`)
47+
console.log(
48+
`\nExported ${pluralize(users.length, 'user email')} to ${filename}`,
49+
)
4750
} catch (error) {
4851
console.error('Error exporting user emails:', error)
4952
}

web/src/app/orgs/[slug]/analytics/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export default function OrganizationAnalyticsPage() {
259259
{analytics.currentBalance.toLocaleString()}
260260
</div>
261261
<p className="text-xs text-muted-foreground">
262-
{pluralize(analytics.currentBalance, 'Credit')} remaining
262+
{pluralize(analytics.currentBalance, 'credit')} remaining
263263
</p>
264264
</CardContent>
265265
</Card>
@@ -276,7 +276,7 @@ export default function OrganizationAnalyticsPage() {
276276
{analytics.usageThisCycle.toLocaleString()}
277277
</div>
278278
<p className="text-xs text-muted-foreground">
279-
{pluralize(analytics.usageThisCycle, 'Credit')} consumed
279+
{pluralize(analytics.usageThisCycle, 'credit')} consumed
280280
</p>
281281
</CardContent>
282282
</Card>
@@ -293,7 +293,7 @@ export default function OrganizationAnalyticsPage() {
293293
{analytics.costProjection.averageDaily.toLocaleString()}
294294
</div>
295295
<p className="text-xs text-muted-foreground">
296-
{pluralize(analytics.costProjection.averageDaily, 'Credit')} per
296+
{pluralize(analytics.costProjection.averageDaily, 'credit')} per
297297
day
298298
</p>
299299
</CardContent>

web/src/app/orgs/[slug]/usage/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ export default function UsagePage() {
293293
{usageData?.topUsers?.length || 0}
294294
</div>
295295
<p className="text-xs text-muted-foreground">
296-
{pluralize(usageData?.topUsers?.length || 0, 'User')} with usage
296+
{pluralize(usageData?.topUsers?.length || 0, 'user')} with usage
297297
</p>
298298
</CardContent>
299299
</Card>

web/src/components/auto-topup/BaseAutoTopupSettingsForm.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { pluralize } from '@codebuff/common/util/string'
12
import { Info } from 'lucide-react'
23
import { useState, useEffect } from 'react'
34

@@ -63,13 +64,9 @@ export function BaseAutoTopupSettingsForm({
6364
// Check threshold limits
6465
useEffect(() => {
6566
if (threshold < MIN_THRESHOLD_CREDITS) {
66-
setThresholdError(
67-
`Minimum ${MIN_THRESHOLD_CREDITS.toLocaleString()} credits`
68-
)
67+
setThresholdError(`Minimum ${pluralize(MIN_THRESHOLD_CREDITS, 'credit')}`)
6968
} else if (threshold > MAX_THRESHOLD_CREDITS) {
70-
setThresholdError(
71-
`Maximum ${MAX_THRESHOLD_CREDITS.toLocaleString()} credits`
72-
)
69+
setThresholdError(`Maximum ${pluralize(MAX_THRESHOLD_CREDITS, 'credit')}`)
7370
} else {
7471
setThresholdError('')
7572
}
@@ -78,13 +75,9 @@ export function BaseAutoTopupSettingsForm({
7875
// Check top-up credit limits
7976
useEffect(() => {
8077
if (topUpAmountCredits < MIN_TOPUP_CREDITS) {
81-
setTopUpCreditsError(
82-
`Minimum ${MIN_TOPUP_CREDITS.toLocaleString()} credits`
83-
)
78+
setTopUpCreditsError(`Minimum ${pluralize(MIN_TOPUP_CREDITS, 'credit')}`)
8479
} else if (topUpAmountCredits > MAX_TOPUP_CREDITS) {
85-
setTopUpCreditsError(
86-
`Maximum ${MAX_TOPUP_CREDITS.toLocaleString()} credits`
87-
)
80+
setTopUpCreditsError(`Maximum ${pluralize(MAX_TOPUP_CREDITS, 'credit')}`)
8881
} else {
8982
setTopUpCreditsError('')
9083
}

web/src/components/credits/CreditPurchaseSection.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { convertCreditsToUsdCents } from '@codebuff/common/util/currency'
2+
import { pluralize } from '@codebuff/common/util/string'
23
import { Loader2 as Loader } from 'lucide-react'
34
import { useState } from 'react'
45

@@ -94,9 +95,9 @@ export function CreditPurchaseSection({
9495
if (isNaN(numCredits)) {
9596
setCustomError('Please enter a valid number')
9697
} else if (numCredits < minCredits) {
97-
setCustomError(`Minimum ${minCredits.toLocaleString()} credits`)
98+
setCustomError(`Minimum ${pluralize(minCredits, 'credit')}`)
9899
} else if (numCredits > maxCredits) {
99-
setCustomError(`Maximum ${maxCredits.toLocaleString()} credits`)
100+
setCustomError(`Maximum ${pluralize(maxCredits, 'credit')}`)
100101
} else {
101102
setCustomError('')
102103
}
@@ -161,7 +162,7 @@ export function CreditPurchaseSection({
161162
max={maxCredits}
162163
value={customCredits}
163164
onChange={(e) => handleCustomCreditsChange(e.target.value)}
164-
placeholder={`${minCredits.toLocaleString()} - ${maxCredits.toLocaleString()} credits`}
165+
placeholder={`${pluralize(minCredits, 'credit')} - ${pluralize(maxCredits, 'credit')}`}
165166
className={cn(customError && 'border-destructive')}
166167
disabled={isProcessing || cooldownActive}
167168
/>

web/src/components/organization/advanced-monitor.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
} from 'lucide-react'
1414
import { useEffect, useState } from 'react'
1515

16+
import { pluralize } from '@codebuff/common/util/string'
17+
1618
import { Badge } from '@/components/ui/badge'
1719
import { Button } from '@/components/ui/button'
1820
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
@@ -206,7 +208,7 @@ export function AdvancedMonitor({
206208
<div className="flex items-center space-x-2">
207209
{getTrendIcon(data.creditVelocity.trend)}
208210
<span className="text-lg font-bold">
209-
{data.creditVelocity.current.toLocaleString()} credits/hour
211+
{pluralize(data.creditVelocity.current, 'credit')}/hour
210212
</span>
211213
</div>
212214
</div>
@@ -250,26 +252,32 @@ export function AdvancedMonitor({
250252
<p className="text-2xl font-bold">
251253
{data.burnRate.daily.toLocaleString()}
252254
</p>
253-
<p className="text-xs text-muted-foreground">Daily</p>
255+
<p className="text-xs text-muted-foreground">
256+
{pluralize(data.burnRate.daily, 'credit')} daily
257+
</p>
254258
</div>
255259
<div className="text-center">
256260
<p className="text-2xl font-bold">
257261
{data.burnRate.weekly.toLocaleString()}
258262
</p>
259-
<p className="text-xs text-muted-foreground">Weekly</p>
263+
<p className="text-xs text-muted-foreground">
264+
{pluralize(data.burnRate.weekly, 'credit')} weekly
265+
</p>
260266
</div>
261267
<div className="text-center">
262268
<p className="text-2xl font-bold">
263269
{data.burnRate.monthly.toLocaleString()}
264270
</p>
265-
<p className="text-xs text-muted-foreground">Monthly</p>
271+
<p className="text-xs text-muted-foreground">
272+
{pluralize(data.burnRate.monthly, 'credit')} monthly
273+
</p>
266274
</div>
267275
</div>
268276
<div className="pt-4 border-t">
269277
<div className="flex items-center justify-between">
270278
<span className="text-sm font-medium">Credits Remaining</span>
271279
<span className="text-lg font-bold">
272-
{data.burnRate.daysRemaining} days
280+
{pluralize(data.burnRate.daysRemaining, 'day')}
273281
</span>
274282
</div>
275283
<Progress
@@ -330,17 +338,23 @@ export function AdvancedMonitor({
330338
<p className="text-2xl font-bold text-red-600">
331339
{data.alerts.critical}
332340
</p>
333-
<p className="text-xs text-muted-foreground">Critical</p>
341+
<p className="text-xs text-muted-foreground">
342+
{pluralize(data.alerts.critical, 'Critical Alert')}
343+
</p>
334344
</div>
335345
<div className="text-center">
336346
<p className="text-2xl font-bold text-yellow-600">
337347
{data.alerts.warnings}
338348
</p>
339-
<p className="text-xs text-muted-foreground">Warnings</p>
349+
<p className="text-xs text-muted-foreground">
350+
{pluralize(data.alerts.warnings, 'Warning')}
351+
</p>
340352
</div>
341353
<div className="text-center">
342354
<p className="text-2xl font-bold">{data.alerts.active}</p>
343-
<p className="text-xs text-muted-foreground">Total</p>
355+
<p className="text-xs text-muted-foreground">
356+
{pluralize(data.alerts.active, 'Alert')} Total
357+
</p>
344358
</div>
345359
</div>
346360
</CardContent>

0 commit comments

Comments
 (0)