Skip to content

feat(Pinyin): add Pinyin service#616

Merged
ArgoZhang merged 3 commits intomasterfrom
feat-pinyin
Oct 18, 2025
Merged

feat(Pinyin): add Pinyin service#616
ArgoZhang merged 3 commits intomasterfrom
feat-pinyin

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Oct 18, 2025

Link issues

fixes #615

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add a new Pinyin service component for detecting Chinese text and extracting Pinyin initials, along with DI registration and integrate it into the solution build.

New Features:

  • Introduce IPinyinService interface for Chinese text detection and Pinyin initial extraction
  • Implement DefaultPinyinService with a static Chinese‐to‐Pinyin mapping for GetFirstLetters and IsChinese methods
  • Add AddPinyinService extension method to register IPinyinService in the dependency injection container

Build:

  • Add BootstrapBlazor.Pinyin project and include it in the solution
  • Update Directory.Build.targets and Frameworks.props to integrate the new Pinyin component into the build

Copilot AI review requested due to automatic review settings October 18, 2025 11:43
@bb-auto bb-auto Bot added the enhancement New feature or request label Oct 18, 2025
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Oct 18, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Oct 18, 2025

Reviewer's Guide

This PR adds a new Pinyin service component by integrating a BootstrapBlazor.Pinyin project into the solution, defining an IPinyinService interface, implementing its logic in DefaultPinyinService with a static character‐to‐pinyin cache, and providing an AddPinyinService extension for DI registration.

Class diagram for the new Pinyin service

classDiagram
    class IPinyinService {
        +bool IsChinese(string text)
        +string GetFirstLetters(string text)
    }
    class DefaultPinyinService {
        +bool IsChinese(string text)
        +string GetFirstLetters(string text)
        +static Dictionary<string, string> Cache
    }
    IPinyinService <|.. DefaultPinyinService
Loading

Class diagram for the service collection extension

classDiagram
    class ServiceCollectionExtension {
        +IServiceCollection AddPinyinService(IServiceCollection services)
    }
Loading

File-Level Changes

Change Details Files
Integrate new Pinyin project into solution and build
  • Added BootstrapBlazor.Pinyin.csproj
  • Updated solution file to include the project
  • Updated Directory.Build.targets and Frameworks.props
BootstrapBlazor.Extensions.slnx
src/Directory.Build.targets
src/Frameworks.props
src/components/BootstrapBlazor.Pinyin/BootstrapBlazor.Pinyin.csproj
Define IPinyinService interface
  • Added interface with IsChinese and GetFirstLetters methods
src/components/BootstrapBlazor.Pinyin/Services/IPinyinService.cs
Implement DefaultPinyinService logic
  • Implemented IsChinese using Unicode range check
  • Implemented GetFirstLetters using a static cache lookup
  • Populated a static Dictionary mapping pinyin keys to Chinese characters
src/components/BootstrapBlazor.Pinyin/Services/DefaultPinyinService.cs
Provide DI extension for IPinyinService
  • Added AddPinyinService extension method using TryAddSingleton
src/components/BootstrapBlazor.Pinyin/Extensions/ServiceCollectionExtension.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#615 Add a Pinyin service to the codebase, including an interface and implementation for Chinese character detection and first-letter extraction.
#615 Provide a DI (Dependency Injection) extension method to register the Pinyin service in the application's service collection.
#615 Create a project structure for the Pinyin service, including necessary files and project configuration.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ArgoZhang ArgoZhang merged commit f19c117 into master Oct 18, 2025
0 of 2 checks passed
@ArgoZhang ArgoZhang deleted the feat-pinyin branch October 18, 2025 11:44
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • GetFirstLetters iterates the entire Cache for each character – consider building a reverse lookup (char→initial) once to improve performance.
  • IsChinese only checks the basic CJK block (0x4E00–0x9FFF); consider including extended CJK ranges or documenting its intended scope.
  • GetFirstLetters silently skips non-Chinese characters; you may want to explicitly handle or preserve them to avoid unexpected empty output.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- GetFirstLetters iterates the entire Cache for each character – consider building a reverse lookup (char→initial) once to improve performance.
- IsChinese only checks the basic CJK block (0x4E00–0x9FFF); consider including extended CJK ranges or documenting its intended scope.
- GetFirstLetters silently skips non-Chinese characters; you may want to explicitly handle or preserve them to avoid unexpected empty output.

## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.Pinyin/Services/DefaultPinyinService.cs:13-25` </location>
<code_context>
+    public string GetFirstLetters(string text)
+    {
+        var ret = new StringBuilder();
+        foreach (var c in text)
+        {
+            foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
+            {
+                ret.Append(item.Key.First());
</code_context>

<issue_to_address>
**suggestion (performance):** Nested loops over Cache for each character may impact performance.

Consider replacing the nested loop with a reverse lookup dictionary to improve performance, especially for long input strings.

```suggestion
    // Reverse lookup dictionary for fast character-to-first-letter mapping
    private static readonly Dictionary<char, char> CharToFirstLetter = BuildCharToFirstLetter();

    public string GetFirstLetters(string text)
    {
        var ret = new StringBuilder();
        foreach (var c in text)
        {
            if (CharToFirstLetter.TryGetValue(c, out var firstLetter))
            {
                ret.Append(firstLetter);
            }
        }
        return ret.ToString().ToUpperInvariant();
    }

    private static Dictionary<char, char> BuildCharToFirstLetter()
    {
        var dict = new Dictionary<char, char>();
        foreach (var kvp in Cache)
        {
            var firstLetter = kvp.Key.First();
            foreach (var ch in kvp.Value)
            {
                if (!dict.ContainsKey(ch))
                {
                    dict[ch] = firstLetter;
                }
            }
        }
        return dict;
    }
```
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.Pinyin/Services/DefaultPinyinService.cs:27-28` </location>
<code_context>
+        return ret.ToString().ToUpperInvariant();
+    }
+
+    public static Dictionary<string, string> Cache = new()
+    {
+        { "a", "阿啊吖嗄腌锕" },
</code_context>

<issue_to_address>
**suggestion:** Consider making Cache readonly to prevent modification.

Marking Cache as readonly will ensure its contents cannot be reassigned, reinforcing its role as a static lookup table.

```suggestion
    public static readonly Dictionary<string, string> Cache = new()
    {
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +13 to +25
public string GetFirstLetters(string text)
{
var ret = new StringBuilder();
foreach (var c in text)
{
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
{
ret.Append(item.Key.First());
break;
}
}
return ret.ToString().ToUpperInvariant();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Nested loops over Cache for each character may impact performance.

Consider replacing the nested loop with a reverse lookup dictionary to improve performance, especially for long input strings.

Suggested change
public string GetFirstLetters(string text)
{
var ret = new StringBuilder();
foreach (var c in text)
{
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
{
ret.Append(item.Key.First());
break;
}
}
return ret.ToString().ToUpperInvariant();
}
// Reverse lookup dictionary for fast character-to-first-letter mapping
private static readonly Dictionary<char, char> CharToFirstLetter = BuildCharToFirstLetter();
public string GetFirstLetters(string text)
{
var ret = new StringBuilder();
foreach (var c in text)
{
if (CharToFirstLetter.TryGetValue(c, out var firstLetter))
{
ret.Append(firstLetter);
}
}
return ret.ToString().ToUpperInvariant();
}
private static Dictionary<char, char> BuildCharToFirstLetter()
{
var dict = new Dictionary<char, char>();
foreach (var kvp in Cache)
{
var firstLetter = kvp.Key.First();
foreach (var ch in kvp.Value)
{
if (!dict.ContainsKey(ch))
{
dict[ch] = firstLetter;
}
}
}
return dict;
}

Comment on lines +27 to +28
public static Dictionary<string, string> Cache = new()
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider making Cache readonly to prevent modification.

Marking Cache as readonly will ensure its contents cannot be reassigned, reinforcing its role as a static lookup table.

Suggested change
public static Dictionary<string, string> Cache = new()
{
public static readonly Dictionary<string, string> Cache = new()
{

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a new Pinyin service to provide Chinese character detection and initial-letter extraction, and wires it into DI. Also updates build/targeting to include .NET 10 and registers the new project in the solution.

  • Introduces IPinyinService and DefaultPinyinService with a large pinyin mapping.
  • Adds DI extension AddPinyinService.
  • Updates targeting to include net10.0 and adjusts a build target condition.

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/components/BootstrapBlazor.Pinyin/Services/IPinyinService.cs Defines the Pinyin service interface.
src/components/BootstrapBlazor.Pinyin/Services/DefaultPinyinService.cs Implements the service with a large static mapping and initial-letter logic.
src/components/BootstrapBlazor.Pinyin/Extensions/ServiceCollectionExtension.cs Adds DI registration for IPinyinService.
src/components/BootstrapBlazor.Pinyin/BootstrapBlazor.Pinyin.csproj New project file for the Pinyin component.
src/Frameworks.props Adds net10.0 to TargetFrameworks.
src/Directory.Build.targets Changes CopyLogo target condition to net10.0.
BootstrapBlazor.Extensions.slnx Adds the Pinyin project to the solution.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +18 to +22
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
{
ret.Append(item.Key.First());
break;
}
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For polyphonic characters (e.g., 重/行/乐), this loop picks the first matching entry by dictionary insertion order, leading to inaccurate initials (e.g., 重 -> 'c' from 'chong' instead of 'z' from 'zhong'). Build a deterministic char->initial mapping with curated defaults or a frequency-based tie-breaker, or return multiple candidates; avoid relying on enumeration order.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +24
var ret = new StringBuilder();
foreach (var c in text)
{
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
{
ret.Append(item.Key.First());
break;
}
}
return ret.ToString().ToUpperInvariant();
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current algorithm is O(n*m): for each input char it scans all 400+ cache entries with string.Contains checks. Precompute a Dictionary<char, char> (or char->string) once from Cache and use O(1) lookups in GetFirstLetters.

Copilot uses AI. Check for mistakes.
var ret = new StringBuilder();
foreach (var c in text)
{
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a char-based check without culture overhead. Prefer item.Value.Contains(c) or item.Value.IndexOf(c) >= 0 (Ordinal), as culture-aware comparisons are unnecessary for Chinese characters and add overhead.

Suggested change
foreach (var item in Cache.Where(item => item.Value.Contains(c, StringComparison.InvariantCulture)))
foreach (var item in Cache.Where(item => item.Value.Contains(c)))

Copilot uses AI. Check for mistakes.
return ret.ToString().ToUpperInvariant();
}

public static Dictionary<string, string> Cache = new()
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposing a public mutable static dictionary invites external mutation and thread-safety issues. Make it private and readonly (e.g., private static readonly IReadOnlyDictionary<string, string> Cache) to protect invariants.

Suggested change
public static Dictionary<string, string> Cache = new()
private static readonly Dictionary<string, string> Cache = new()

Copilot uses AI. Check for mistakes.

class DefaultPinyinService : IPinyinService
{
public bool IsChinese(string text) => text.Any(i => i >= 0x4E00 && i <= 0x9FFF);
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only matches the basic CJK block and will miss many Chinese characters (e.g., Extension A and CJK Compatibility Ideographs). Consider extending the ranges: (0x3400–0x4DBF), (0x4E00–0x9FFF), and (0xF900–0xFAFF). Note: Extension B+ are surrogate pairs and can't be handled by a single char.

Suggested change
public bool IsChinese(string text) => text.Any(i => i >= 0x4E00 && i <= 0x9FFF);
public bool IsChinese(string text) =>
text.Any(i =>
(i >= 0x3400 && i <= 0x4DBF) || // CJK Unified Ideographs Extension A
(i >= 0x4E00 && i <= 0x9FFF) || // CJK Unified Ideographs
(i >= 0xF900 && i <= 0xFAFF) // CJK Compatibility Ideographs
);

Copilot uses AI. Check for mistakes.
<Project>

<Target Name="CopyLogo" AfterTargets="Build" Condition="'$(TargetFramework)' == 'net9.0'">
<Target Name="CopyLogo" AfterTargets="Build" Condition="'$(TargetFramework)' == 'net10.0'">
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the condition to only net10.0 stops copying the logo for net9.0 builds. If both are intended, update the condition to include both frameworks, e.g.: Condition="'$(TargetFramework)' == 'net9.0' Or '$(TargetFramework)' == 'net10.0'".

Suggested change
<Target Name="CopyLogo" AfterTargets="Build" Condition="'$(TargetFramework)' == 'net10.0'">
<Target Name="CopyLogo" AfterTargets="Build" Condition="'$(TargetFramework)' == 'net9.0' Or '$(TargetFramework)' == 'net10.0'">

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(Pinyin): add Pinyin service

2 participants