DEV Community

junhao li
junhao li

Posted on

From Discovery to Creation: How I Built a Japanese Text Counter Tool Inspired by Kantan Tools

The Beginning: A Serendipitous Discovery

As a developer, I'm constantly on the lookout for useful online tools that can boost productivity. One day, I stumbled upon Kantan Tools, a beautifully crafted collection of web utilities that immediately caught my attention. The clean design and practical functionality were impressive, but what really drew me in was their character counter tool.

What I loved about Kantan Tools:

  • ๐Ÿš€ Lightning Fast: Client-side processing, no registration required
  • ๐Ÿ”’ Privacy First: All data processed locally, nothing sent to servers
  • ๐Ÿ› ๏ธ Feature Rich: From character counting to password generation, it had everything

But as a developer with a chronic case of "not-invented-here syndrome," I started wondering: Could I create a specialized tool focused specifically on Japanese text counting?

From Idea to Reality: Building TextCounter-JP

And that's how TextCounter-JP was born. My goal was to create a Japanese-optimized text counter that would go beyond basic character counting.

๐ŸŽฏ Core Feature Design

1. Multi-dimensional Text Analysis

  • Basic Stats: Character count, word count, line count
  • Japanese-Specific: Separate counts for ใฒใ‚‰ใŒใช (Hiragana), ใ‚ซใ‚ฟใ‚ซใƒŠ (Katakana), and ๆผขๅญ— (Kanji)
  • Practical Metrics: Manuscript paper calculation, byte count in various encodings

2. Japanese Manuscript Paper Calculation

This addresses a specific need in Japanese document formatting:

// 400-character manuscript paper calculation logic
const calculateManuscriptPaper = (text, charsPerLine = 20, linesPerPage = 20) => {
  const lines = text.split('\n');
  let totalPages = 0;
  let currentPageLines = 0;

  lines.forEach(line => {
    if (line.length === 0) {
      // Empty lines still count as one line
      currentPageLines++;
    } else {
      // Calculate how many manuscript lines this text line needs
      const linesNeeded = Math.ceil(line.length / charsPerLine);
      currentPageLines += linesNeeded;
    }

    // Check if we need a new page
    if (currentPageLines >= linesPerPage) {
      totalPages += Math.ceil(currentPageLines / linesPerPage);
      currentPageLines = currentPageLines % linesPerPage;
    }
  });

  return totalPages || 1;
};
Enter fullscreen mode Exit fullscreen mode

3. Multi-Encoding Byte Calculation

// Support for multiple Japanese text encodings
const calculateBytes = (text) => {
  const encodings = {
    'UTF-8': new TextEncoder().encode(text).length,
    'Shift-JIS': calculateShiftJISBytes(text),
    'EUC-JP': calculateEUCJPBytes(text),
    'ISO-2022-JP': calculateJISBytes(text)
  };
  return encodings;
};

// Shift-JIS byte calculation (simplified)
const calculateShiftJISBytes = (text) => {
  let bytes = 0;
  for (let i = 0; i < text.length; i++) {
    const code = text.charCodeAt(i);
    if (code < 0x80) {
      bytes += 1; // ASCII characters
    } else if (code >= 0xFF61 && code <= 0xFF9F) {
      bytes += 1; // Half-width katakana
    } else {
      bytes += 2; // Full-width characters
    }
  }
  return bytes;
};
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ง Technical Implementation Details

1. Real-time Calculation with Performance Optimization

// Use debounce to avoid excessive calculations
const debouncedCount = debounce((text) => {
  updateAllCounters(text);
}, 100);

// Large text processing optimization  
const processLargeText = (text) => {
  if (text.length > 10000) {
    // For large texts, use requestIdleCallback for chunked processing
    requestIdleCallback(() => {
      calculateDetailedStats(text);
    });
  } else {
    calculateDetailedStats(text);
  }
};

// Debounce utility function
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}
Enter fullscreen mode Exit fullscreen mode

2. Precise Japanese Character Classification

// Accurate Japanese character type identification
const analyzeJapaneseText = (text) => {
  const patterns = {
    hiragana: /[\u3040-\u309F]/g,
    katakana: /[\u30A0-\u30FF]/g,
    kanji: /[\u4E00-\u9FAF]/g,
    halfWidthKana: /[\uFF65-\uFF9F]/g,
    punctuation: /[\u3000-\u303F]/g,
    ascii: /[\x00-\x7F]/g
  };

  const results = {};
  for (const [type, regex] of Object.entries(patterns)) {
    results[type] = (text.match(regex) || []).length;
  }

  // Combine full-width and half-width katakana
  results.katakana += results.halfWidthKana;
  delete results.halfWidthKana;

  return results;
};
Enter fullscreen mode Exit fullscreen mode

3. Responsive Design Implementation

Using CSS Grid and Flexbox for device-adaptive layouts:

.stats-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
}

.stat-card {
  background: #f8f9fa;
  border-radius: 8px;
  padding: 1.5rem;
  border-left: 4px solid #007bff;
  transition: transform 0.2s ease;
}

.stat-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

@media (max-width: 768px) {
  .stats-container {
    grid-template-columns: 1fr;
  }

  .text-input {
    min-height: 200px;
    font-size: 16px; /* Prevent zoom on iOS */
  }

  .stat-card {
    padding: 1rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Š User Experience Enhancements

1. Progressive Feature Disclosure

// Tab-based interface for organizing features
class TabManager {
  constructor() {
    this.activeTab = 'basic';
    this.tabs = {
      basic: 'Basic Statistics',
      advanced: 'Advanced Analysis', 
      manuscript: 'Manuscript Paper',
      encoding: 'Byte Encoding'
    };
  }

  switchTab(tabId) {
    // Hide all tab contents
    document.querySelectorAll('.tab-content').forEach(tab => {
      tab.classList.remove('active');
    });

    // Show selected tab
    document.getElementById(`${tabId}-tab`).classList.add('active');

    // Update tab buttons
    document.querySelectorAll('.tab-button').forEach(btn => {
      btn.classList.toggle('active', btn.dataset.tab === tabId);
    });

    this.activeTab = tabId;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Accessibility Features

<!-- Semantic HTML structure -->
<main role="main" aria-labelledby="main-heading">
  <h1 id="main-heading">Japanese Text Counter</h1>

  <section aria-labelledby="input-section">
    <h2 id="input-section">Text Input</h2>
    <label for="text-input" class="sr-only">
      Enter text to count characters
    </label>
    <textarea 
      id="text-input"
      aria-describedby="input-help"
      placeholder="Enter your Japanese text here..."
      rows="8">
    </textarea>
    <p id="input-help" class="help-text">
      Text is processed locally in your browser for privacy
    </p>
  </section>

  <section aria-labelledby="results-section">
    <h2 id="results-section">Analysis Results</h2>
    <div role="tablist" aria-labelledby="results-section">
      <button role="tab" aria-selected="true" aria-controls="basic-panel">
        Basic Stats
      </button>
      <!-- More tabs... -->
    </div>
    <div role="tabpanel" id="basic-panel" aria-labelledby="basic-tab">
      <!-- Results content -->
    </div>
  </section>
</main>
Enter fullscreen mode Exit fullscreen mode

3. Performance Monitoring

// Simple performance tracking
class PerformanceMonitor {
  static trackCalculation(textLength, calculationType) {
    const start = performance.now();

    return {
      end: () => {
        const duration = performance.now() - start;

        // Log slow operations (>100ms)
        if (duration > 100) {
          console.warn(`Slow ${calculationType} calculation:`, {
            textLength,
            duration: `${duration.toFixed(2)}ms`
          });
        }

        return duration;
      }
    };
  }
}

// Usage example
const monitor = PerformanceMonitor.trackCalculation(text.length, 'full-analysis');
const results = analyzeJapaneseText(text);
const duration = monitor.end();
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ Deployment and Optimization

Performance Optimization Strategy

  1. Asset Optimization: CSS/JS minification and combination
  2. Caching Strategy: Aggressive browser caching for static assets
  3. CDN Distribution: Static assets served via CDN for global performance
// Service Worker for caching (simplified)
const CACHE_NAME = 'textcounter-jp-v1';
const urlsToCache = [
  '/',
  '/css/styles.min.css',
  '/js/app.min.js',
  '/fonts/NotoSansJP-Regular.woff2'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});
Enter fullscreen mode Exit fullscreen mode

SEO Optimization

  • Keyword Targeting: Optimized for "ๆ–‡ๅญ—ๆ•ฐใ‚ซใ‚ฆใƒณใƒˆ", "Japanese character counter"
  • Structured Data: JSON-LD markup for better search visibility
  • Mobile-First Design: Responsive design that works on all devices
<!-- Structured data for search engines -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebApplication",
  "name": "TextCounter-JP",
  "description": "Professional Japanese text analysis tool with character counting, manuscript paper calculation, and multi-encoding support",
  "url": "https://textcounter-jp.com",
  "applicationCategory": "UtilityApplication",
  "operatingSystem": "Any",
  "offers": {
    "@type": "Offer",
    "price": "0"
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ญ Development Insights and Lessons Learned

Key Takeaways

  1. User-Centric Design: Specialized tools often outperform generic ones
  2. Localization Matters: Language-specific optimizations add significant value
  3. Performance vs Features: Real-time processing requires careful balance
  4. Privacy by Design: Local processing is increasingly important to users

Technical Stack Decisions

  • Frontend: Vanilla JavaScript + CSS3 (for maximum compatibility)
  • Build Tools: Webpack + Babel for modern JS features
  • Deployment: Static site hosting (Netlify/Vercel)
  • Analytics: Privacy-focused tracking with Plausible

Architecture Decisions

// Modular architecture for maintainability
class TextAnalyzer {
  constructor() {
    this.processors = {
      basic: new BasicStatsProcessor(),
      japanese: new JapaneseAnalysisProcessor(),
      encoding: new EncodingProcessor(),
      manuscript: new ManuscriptProcessor()
    };
  }

  analyze(text, options = {}) {
    const results = {};

    for (const [type, processor] of Object.entries(this.processors)) {
      if (options.include ? options.include.includes(type) : true) {
        results[type] = processor.process(text);
      }
    }

    return results;
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ฎ Future Roadmap

Planned Features

  1. File Processing:

    • Direct PDF text extraction
    • Batch file processing
    • Document format conversion
  2. Advanced Analytics:

    • Reading difficulty assessment
    • Kanji level analysis (JLPT levels)
    • Text complexity scoring
  3. Developer APIs:

    • RESTful API for text analysis
    • NPM package for Node.js integration
    • Browser extension

Technical Improvements

// Planned WebAssembly integration for performance
class WasmTextProcessor {
  async initialize() {
    this.wasmModule = await import('./text-processor.wasm');
    this.initialized = true;
  }

  processText(text) {
    if (!this.initialized) {
      throw new Error('WASM module not initialized');
    }

    // Leverage WASM for intensive text processing
    return this.wasmModule.analyze_japanese_text(text);
  }
}
Enter fullscreen mode Exit fullscreen mode

Community Features

  • User Contributions: Community-driven feature requests
  • Open Source: Planning to open-source the core algorithms
  • Educational Content: Tutorials on Japanese text processing

Conclusion

Building TextCounter-JP taught me that great ideas often come from improving existing solutions rather than starting from scratch. While Kantan Tools provided the initial inspiration, focusing on the specific needs of Japanese text processing allowed me to create something truly specialized.

The journey from discovering a cool tool to shipping my own has been incredibly rewarding. It's a reminder that in our interconnected world, we're all building on each other's work, and that's what makes the developer community so amazing.

Have you ever been inspired by a tool to build your own version? I'd love to hear about your experiences in the comments below!


๐Ÿ”— Links:

๐Ÿ’ฌ Let's Connect: What tools have inspired your next project? Share your stories below!


Tags: #webdev #javascript #tools #japanese #frontend #productivity #textprocessing #i18n

Top comments (0)